From 81d97ce6c15edb1a3f3cf554317fe4be37e3d128 Mon Sep 17 00:00:00 2001 From: weishen Date: Fri, 24 Apr 2026 15:16:42 +0800 Subject: [PATCH] Sync: update data model docs --- Project/fonrey/DATA_MODEL/DATA_MODEL.md | 803 +----------- .../fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md | 1168 +++++++++++++++++ .../DATA_MODEL/diagram/fonrey-er.drawio | 860 ++++++++++++ .../fonrey/DATA_MODEL/fonrey-data-model.xml | 1132 +++++----------- .../DATA_MODEL/fonrey-data-model.xml.bak | 774 +++++++++++ wiki/index.md | 2 +- wiki/log.md | 12 + wiki/overview.md | 4 +- ...pic-56-automated-infrastructure-testing.md | 58 + 9 files changed, 3281 insertions(+), 1532 deletions(-) create mode 100644 Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md create mode 100644 Project/fonrey/DATA_MODEL/diagram/fonrey-er.drawio create mode 100644 Project/fonrey/DATA_MODEL/fonrey-data-model.xml.bak create mode 100644 wiki/sources/ctp-topic-56-automated-infrastructure-testing.md diff --git a/Project/fonrey/DATA_MODEL/DATA_MODEL.md b/Project/fonrey/DATA_MODEL/DATA_MODEL.md index 35e324a6..d8ac29c0 100644 --- a/Project/fonrey/DATA_MODEL/DATA_MODEL.md +++ b/Project/fonrey/DATA_MODEL/DATA_MODEL.md @@ -87,7 +87,7 @@ | **Complex(楼盘/小区)** | `complexes` → [DATA_MODEL_COMPLEX.md](./DATA_MODEL_COMPLEX.md) | 房源录入的基础底座,维护楼盘标准名称/坐标/锁定状态/别名等 | | **Building(楼栋/单元)** | `buildings` → [DATA_MODEL_COMPLEX.md](./DATA_MODEL_COMPLEX.md) | 楼盘下的物理楼栋,区分标准结构与非标结构 | | **RoomUnit(房号)** | `room_units` → [DATA_MODEL_COMPLEX.md](./DATA_MODEL_COMPLEX.md) | 楼层+房间号,房源定位的最细粒度 | -| **Property(房源)** | `properties` → §3.3 | 系统核心表,每套二手房源的完整档案,支持出售/出租/出售兼出租三态 | +| **Property(房源)** | `properties` → [DATA_MODEL_PROPERTY.md](./DATA_MODEL_PROPERTY.md) | 系统核心表,每套二手房源的完整档案,支持出售/出租/出售兼出租三态 | | **Client(客源)** | `clients` → [DATA_MODEL_CLIENT.md](./DATA_MODEL_CLIENT.md) | 买家/租客档案,分私客/公客/成交客,含活跃度评分与自动公客转换机制 | | **Viewing(带看)** | `client_viewings` → [DATA_MODEL_CLIENT.md](./DATA_MODEL_CLIENT.md) | 经纪人带客户看房的完整记录 | | **Match(配对)** | `client_property_matches` → [DATA_MODEL_CLIENT.md](./DATA_MODEL_CLIENT.md) | 系统/人工推荐的客源↔房源配对 | @@ -117,7 +117,7 @@ OrgUnit (组织架构) | [DATA_MODEL_ORG.md](./DATA_MODEL_ORG.md) | 组织人事(org_units, staff, 异动/奖惩/教育/家庭等) | ✅ 完成 | | [DATA_MODEL_COMPLEX.md](./DATA_MODEL_COMPLEX.md) | 楼盘/区域(districts, business_areas, complexes, buildings, room_units, schools 等) | ✅ 完成 | | [DATA_MODEL_CLIENT.md](./DATA_MODEL_CLIENT.md) | 客源管理(clients, requirements, follow_logs, viewings, matches 等) | ✅ 完成 | -| 本文档 §3.3–§3.16 | 房源核心(properties 及配套 12 张表)、系统设置 | ✅ 完成 | +| [DATA_MODEL_PROPERTY.md](./DATA_MODEL_PROPERTY.md) | 房源管理(properties 及配套 22 张表,含跟进/钥匙/委托/实勘/营销/产证/完成度等) | ✅ 完成 | --- @@ -220,765 +220,46 @@ CREATE INDEX idx_domains_primary ON public.domains(tenant_id) WHERE is_primary = --- -### 3.3 房源核心模块(Property Core) - -```sql --- ============================================================ --- 房源主表:系统最核心的表,全部筛选/排序/搜索围绕此表展开 --- 设计重点:89,000+ 数据量,复合索引策略,分区预留 --- ============================================================ - -CREATE TABLE properties ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - - -- ── 基础分类 ── - property_type VARCHAR(20) NOT NULL - CHECK (property_type IN ('residential','villa','commercial_residential', - 'shop','office','other')), - -- residential=住宅, villa=别墅, commercial_residential=商住, - -- shop=商铺, office=写字楼, other=其他 - - -- ── 交易状态 ── - status VARCHAR(20) NOT NULL DEFAULT 'for_sale' - CHECK (status IN ('for_sale','for_rent','for_sale_rent', - 'suspended','sold_elsewhere','rented_elsewhere', - 'sold','unlisted')), - -- for_sale=出售, for_rent=出租, for_sale_rent=租售, - -- suspended=暂缓, sold_elsewhere=他售, rented_elsewhere=他租, - -- sold=成交, unlisted=未挂牌 - - -- ── 流通属性 ── - attribute VARCHAR(20) NOT NULL DEFAULT 'public' - CHECK (attribute IN ('public','private','special','sealed')), - -- public=公盘, private=私盘, special=特盘, sealed=封盘 - private_reason TEXT, -- 私盘/封盘必填说明 - - -- ── 位置信息 ── - complex_id UUID NOT NULL REFERENCES complexes(id) ON DELETE RESTRICT, - building_id UUID REFERENCES buildings(id) ON DELETE SET NULL, - block_no VARCHAR(30), -- 栋/幢/弄号 - unit_no VARCHAR(30), -- 单元号 - room_no VARCHAR(30), -- 房号/门牌号 - floor SMALLINT NOT NULL, -- 所在楼层 - total_floors SMALLINT NOT NULL, -- 总楼层 - CONSTRAINT chk_floor CHECK (floor > 0 AND floor <= total_floors), - - -- ── 户型 ── - bedroom_count SMALLINT NOT NULL DEFAULT 0, -- 室 - living_room_count SMALLINT NOT NULL DEFAULT 0, -- 厅 - bathroom_count SMALLINT NOT NULL DEFAULT 0, -- 卫 - kitchen_count SMALLINT NOT NULL DEFAULT 0, -- 厨 - balcony_count SMALLINT NOT NULL DEFAULT 0, -- 阳台数 - - -- ── 面积 ── - area NUMERIC(8,2) NOT NULL, -- 建筑面积 m² - inner_area NUMERIC(8,2), -- 套内面积 m²(编辑时填写) - - -- ── 价格 ── - sale_price NUMERIC(12,2), -- 挂牌售价(万元) - sale_bottom_price NUMERIC(12,2), -- 售底价(万元,内部可见) - sale_record_price NUMERIC(12,2), -- 备案/核验价(万元) - rent_price NUMERIC(10,2), -- 挂牌租价(元/月) - - -- ── 基础物理属性 ── - orientation VARCHAR(10) - CHECK (orientation IN ('east','south','west','north', - 'southeast','northeast','east_west', - 'south_north','northwest','southwest')), - decoration VARCHAR(10) - CHECK (decoration IN ('rough','plain','simple','medium', - 'fine','luxury')), - -- rough=毛坯, plain=清水, simple=简装, medium=中装, fine=精装, luxury=豪装 - - has_elevator BOOLEAN, - built_year SMALLINT, - - -- ── 用途 ── - usage_type VARCHAR(30), -- 住宅/商住/商业/普通住宅/花园洋房 等 - usage_subtype VARCHAR(30), -- 细分用途 - - -- ── 商铺专属 ── - shop_frontage NUMERIC(6,2), -- 开间(米) - shop_depth NUMERIC(6,2), -- 进深(米) - shop_height NUMERIC(6,2), -- 层高(米) - shop_location VARCHAR(20) - CHECK (shop_location IS NULL OR - shop_location IN ('street','mall','residential', - 'ground_floor','complex')), - - -- ── 房屋状态 ── - house_status VARCHAR(20) - CHECK (house_status IN ('owner_occupied','vacant', - 'tenant_occupied','unknown')), - viewing_time VARCHAR(20) - CHECK (viewing_time IN ('anytime','by_appointment','inconvenient')), - - -- ── 等级与标签 ── - grade VARCHAR(5) - CHECK (grade IN ('A_urgent','A','B','C','D')), - -- A_urgent=A(急迫), A=A, B=B(较强), C=C(一般), D=D - - -- ── 交易属性 ── - ownership_years VARCHAR(30), -- 房本年限:不满2年/满2年/满5年 等 - ownership_years_detail VARCHAR(20), -- 满五/不满五 - ownership_nature VARCHAR(20) - CHECK (ownership_nature IS NULL OR - ownership_nature IN ('commercial','reform_housing', - 'collective','economic')), - -- commercial=商品房, reform_housing=房改房, collective=集资房, economic=经济适用房 - is_only_house BOOLEAN, -- 唯一住房 - payment_method VARCHAR(30) - CHECK (payment_method IS NULL OR - payment_method IN ('full','mortgage','installment','advance')), - tax_included VARCHAR(10) - CHECK (tax_included IS NULL OR - tax_included IN ('each_party','net','inclusive')), - has_mortgage BOOLEAN, - has_loan BOOLEAN, - has_seal BOOLEAN, - has_restriction BOOLEAN, - original_price NUMERIC(12,2), -- 原购价(万元) - sale_reason TEXT, -- 售房原因(最多200字) - - -- ── 营销备注 ── - remarks TEXT, -- 房源备注(最多500字) - - -- ── 相关方(冗余存储 UUID,完整信息查 staff 表)── - first_recorder_id UUID REFERENCES staff(id) ON DELETE SET NULL, -- 首录方 - number_holder_id UUID REFERENCES staff(id) ON DELETE SET NULL, -- 号码方 - seller_agent_id UUID REFERENCES staff(id) ON DELETE SET NULL, -- 出售方 - buyer_agent_id UUID REFERENCES staff(id) ON DELETE SET NULL, -- 实买方 - - -- ── 来源 ── - source VARCHAR(50), -- 房源来源渠道(由运营维护枚举) - - -- ── 维护完成度(冗余缓存,定期重算)── - completeness_score SMALLINT NOT NULL DEFAULT 0, -- 0-100 分 - - -- ── 时间轨迹 ── - listed_at TIMESTAMPTZ, -- 最近一次挂牌时间 - last_followed_at TIMESTAMPTZ, -- 最后跟进时间(冗余,加速排序) - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - deleted_at TIMESTAMPTZ, - created_by UUID REFERENCES staff(id) ON DELETE SET NULL, - updated_by UUID REFERENCES staff(id) ON DELETE SET NULL, - - -- ── 全文检索向量 ── - search_vector TSVECTOR -); - --- ── 索引策略(针对高频查询路径设计)── - --- 1. 最核心的列表页:按状态 + 属性 + 类型过滤 -CREATE INDEX idx_properties_status_attr ON properties(status, attribute, property_type) - WHERE deleted_at IS NULL; - --- 2. 区域筛选(通过 complex 表 JOIN 优化) -CREATE INDEX idx_properties_complex ON properties(complex_id) - WHERE deleted_at IS NULL; - --- 3. 价格排序(出售最常用) -CREATE INDEX idx_properties_sale_price ON properties(sale_price DESC NULLS LAST) - WHERE deleted_at IS NULL AND status IN ('for_sale','for_sale_rent'); - --- 4. 面积区间筛选 -CREATE INDEX idx_properties_area ON properties(area) - WHERE deleted_at IS NULL; - --- 5. 挂牌日期倒序(最新挂牌) -CREATE INDEX idx_properties_listed_at ON properties(listed_at DESC NULLS LAST) - WHERE deleted_at IS NULL; - --- 6. 最后跟进日期(超时未跟进功能) -CREATE INDEX idx_properties_last_followed ON properties(last_followed_at DESC NULLS LAST) - WHERE deleted_at IS NULL; - --- 7. 户型筛选 -CREATE INDEX idx_properties_bedroom ON properties(bedroom_count) - WHERE deleted_at IS NULL; - --- 8. 等级筛选 -CREATE INDEX idx_properties_grade ON properties(grade) - WHERE deleted_at IS NULL; - --- 9. 完成度排序(引导补全信息) -CREATE INDEX idx_properties_completeness ON properties(completeness_score) - WHERE deleted_at IS NULL; - --- 10. 全文搜索 -CREATE INDEX idx_properties_search ON properties USING gin(search_vector); - --- 11. 与我相关(相关方快速定位) -CREATE INDEX idx_properties_seller_agent ON properties(seller_agent_id) - WHERE deleted_at IS NULL; -CREATE INDEX idx_properties_number_holder ON properties(number_holder_id) - WHERE deleted_at IS NULL; - --- 12. 复合索引:列表默认排序(状态 + 挂牌时间) -CREATE INDEX idx_properties_list_default ON properties(status, listed_at DESC NULLS LAST) - WHERE deleted_at IS NULL; -``` - ---- - -### 3.4 房源联系人(Property Contacts) - -```sql --- ============================================================ --- 业主/联系人:手机号加密存储,哈希值支持重复检测 --- 安全要点:任何查看明文号码的行为均触发审计日志 --- ============================================================ - -CREATE TABLE property_contacts ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, - - name VARCHAR(50) NOT NULL, - gender VARCHAR(10) NOT NULL DEFAULT 'male' - CHECK (gender IN ('male','female')), - identity VARCHAR(20) NOT NULL DEFAULT 'contact' - CHECK (identity IN ('owner','contact','subletter', - 'tenant','agent','corporate')), - -- owner=业主, contact=联系人, subletter=二房东, tenant=租客, - -- agent=代理人, corporate=企业法人 - - -- 手机号:加密存储 + 哈希索引(重复检测用) - phone_enc BYTEA NOT NULL, -- AES-256-GCM 加密 - phone_hash VARCHAR(64) NOT NULL, -- SHA-256(phone) 用于去重查询 - phone2_enc BYTEA, - phone2_hash VARCHAR(64), - - wechat VARCHAR(100), -- 微信号(相对不敏感,可明文) - qq VARCHAR(20), - remarks TEXT, - - -- 是否为号码方(关联审批流) - is_number_holder BOOLEAN NOT NULL DEFAULT FALSE, - number_holder_approved_at TIMESTAMPTZ, -- 审批通过时间 - - sort_order INTEGER NOT NULL DEFAULT 0, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - deleted_at TIMESTAMPTZ, - created_by UUID REFERENCES staff(id) ON DELETE SET NULL, - updated_by UUID REFERENCES staff(id) ON DELETE SET NULL -); - -CREATE INDEX idx_contacts_property ON property_contacts(property_id) - WHERE deleted_at IS NULL; - --- 关键:手机号哈希全局索引(用于重复房源检测) -CREATE INDEX idx_contacts_phone_hash ON property_contacts(phone_hash) - WHERE deleted_at IS NULL; -CREATE INDEX idx_contacts_phone2_hash ON property_contacts(phone2_hash) - WHERE phone2_hash IS NOT NULL AND deleted_at IS NULL; -``` - ---- - -### 3.5 挂牌历史(Listing History) - -```sql --- ============================================================ --- 挂牌历史:记录房源每次上架的完整快照 --- 设计重点:不可删除(合规),仅追加 --- ============================================================ - -CREATE TABLE listing_histories ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - property_id UUID NOT NULL REFERENCES properties(id) ON DELETE RESTRICT, - - listing_type VARCHAR(20) NOT NULL - CHECK (listing_type IN ('for_sale','for_rent')), - status VARCHAR(20) NOT NULL DEFAULT 'active' - CHECK (status IN ('active','ended')), - - -- 价格快照 - sale_price NUMERIC(12,2), - rent_price NUMERIC(10,2), - sale_unit_price NUMERIC(10,2), -- 元/m²,计算字段 - - -- 交易信息快照 - ownership_years VARCHAR(30), - is_only_house BOOLEAN, - tax_included VARCHAR(10), - sale_reason TEXT, - - -- 经纪人快照 - seller_agent_id UUID REFERENCES staff(id) ON DELETE SET NULL, - seller_agent_snapshot JSONB, -- 存储经纪人姓名+门店(防止变更后丢失) - - started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - ended_at TIMESTAMPTZ, - - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() - -- 注:无 deleted_at,此表记录不可删除 -); - -CREATE INDEX idx_listing_histories_property ON listing_histories(property_id); -CREATE INDEX idx_listing_histories_active ON listing_histories(property_id) - WHERE status = 'active'; -``` - ---- - -### 3.6 调价记录(Price Change Log) - -```sql --- ============================================================ --- 调价记录:支持折线图展示,不可删除 --- ============================================================ - -CREATE TABLE price_changes ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - property_id UUID NOT NULL REFERENCES properties(id) ON DELETE RESTRICT, - - old_sale_price NUMERIC(12,2), - new_sale_price NUMERIC(12,2), - old_bottom_price NUMERIC(12,2), - new_bottom_price NUMERIC(12,2), - old_record_price NUMERIC(12,2), - new_record_price NUMERIC(12,2), - old_rent_price NUMERIC(10,2), - new_rent_price NUMERIC(10,2), - - change_reason TEXT NOT NULL, -- 最多200字 - changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - changed_by UUID NOT NULL REFERENCES staff(id) ON DELETE RESTRICT -); - -CREATE INDEX idx_price_changes_property ON price_changes(property_id); -CREATE INDEX idx_price_changes_time ON price_changes(property_id, changed_at DESC); -``` - ---- - -### 3.7 跟进日志(Follow-up Logs) - -```sql --- ============================================================ --- 跟进日志:系统最高写入频率的表,按 property_id 分区预留 --- 6 种类型:写入跟进/修改跟进/敏感信息跟进/敏感信息查看/其他跟进/系统日志 --- ============================================================ - -CREATE TABLE follow_logs ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, - - log_type VARCHAR(30) NOT NULL - CHECK (log_type IN ('written','modified','sensitive_op', - 'sensitive_view','other','system')), - -- written=写入跟进(经纪人主动写) - -- modified=修改跟进(字段变更自动生成) - -- sensitive_op=敏感信息跟进(相关方保护变更) - -- sensitive_view=敏感信息查看(查看号码等) - -- other=其他跟进(钥匙/新增联系人等) - -- system=系统日志 - - -- 写入跟进专用字段 - purpose VARCHAR(50), -- 跟进目的(由运营维护枚举值) - content TEXT, -- 跟进内容,最少6字最多500字 - ai_tag VARCHAR(20) - CHECK (ai_tag IS NULL OR ai_tag IN ('ai_for_sale','ai_not_for_sale')), - - -- 修改跟进专用字段 - change_detail JSONB, - -- 格式:{"field": "sale_price", "old": 850, "new": 800, "label": "售价"} - -- 支持多字段同时变更 - - -- 系统标签(显示在日志时间线上的 tag) - log_tag VARCHAR(50), - -- 如:查看号码/图片下载/改状态/改价格/改等级/修改相关方 等 - - -- 可见性控制 - is_public BOOLEAN NOT NULL DEFAULT TRUE, - -- FALSE = 仅本人及上级可见 - - -- 操作人 - operator_id UUID REFERENCES staff(id) ON DELETE SET NULL, - operator_snapshot JSONB, -- {name, role, org_unit_name, store_group} - - -- 是否可删除(敏感信息查看类型 = FALSE) - is_deletable BOOLEAN NOT NULL DEFAULT TRUE, - - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - deleted_at TIMESTAMPTZ -- 仅 is_deletable=TRUE 时才能软删 -); - --- 核心索引:时间线展示 -CREATE INDEX idx_follow_logs_property_time ON follow_logs(property_id, created_at DESC) - WHERE deleted_at IS NULL; - --- 按类型过滤(6个 Tab 查询) -CREATE INDEX idx_follow_logs_type ON follow_logs(property_id, log_type, created_at DESC) - WHERE deleted_at IS NULL; - --- 操作员过滤(跟进日志搜索功能) -CREATE INDEX idx_follow_logs_operator ON follow_logs(operator_id, created_at DESC) - WHERE deleted_at IS NULL; - --- 不可删除类型专用索引(合规审计) -CREATE INDEX idx_follow_logs_sensitive ON follow_logs(property_id, created_at DESC) - WHERE log_type IN ('sensitive_view','sensitive_op'); - --- 跟进日志附件(一条跟进可附多张图) -CREATE TABLE follow_log_attachments ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - follow_log_id UUID NOT NULL REFERENCES follow_logs(id) ON DELETE CASCADE, - file_key TEXT NOT NULL, -- R2/S3 存储路径 - file_name VARCHAR(255) NOT NULL, - file_size INTEGER NOT NULL, -- bytes - file_type VARCHAR(10) - CHECK (file_type IN ('bmp','jpg','png','svg','gif')), - sort_order SMALLINT NOT NULL DEFAULT 0, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE INDEX idx_follow_attachments_log ON follow_log_attachments(follow_log_id); - --- 跟进录音(独立存储,支持音频文件) -CREATE TABLE follow_log_recordings ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - follow_log_id UUID NOT NULL REFERENCES follow_logs(id) ON DELETE CASCADE, - file_key TEXT NOT NULL, - duration_seconds INTEGER, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); -``` - ---- - -### 3.8 钥匙管理(Key Management) - -```sql -CREATE TABLE property_keys ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, - - key_type VARCHAR(20) NOT NULL - CHECK (key_type IN ('mechanical','password')), - - -- 钥匙持有方 - holder_id UUID REFERENCES staff(id) ON DELETE SET NULL, - holder_snapshot JSONB, -- {name, store_group}(防人员变动丢失) - storage_unit_id UUID REFERENCES org_units(id) ON DELETE SET NULL, -- 保管部门 - - -- 他司钥匙标记 - is_other_agency BOOLEAN NOT NULL DEFAULT FALSE, - other_agency_info VARCHAR(30), -- 他司信息,最多30字 - - remarks TEXT, -- 备注,最多200字 - - is_active BOOLEAN NOT NULL DEFAULT TRUE, -- FALSE = 钥匙已归还/失效 - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - created_by UUID REFERENCES staff(id) ON DELETE SET NULL -); - -CREATE INDEX idx_property_keys_property ON property_keys(property_id) - WHERE is_active = TRUE; - --- 钥匙附件 -CREATE TABLE key_attachments ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - key_id UUID NOT NULL REFERENCES property_keys(id) ON DELETE CASCADE, - file_key TEXT NOT NULL, - file_name VARCHAR(255) NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); -``` - ---- - -### 3.9 委托管理(Commission Management) - -```sql -CREATE TABLE commissions ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, - - commission_type VARCHAR(50) NOT NULL, -- 独家委托/非独家委托(运营维护枚举) - period_start DATE NOT NULL, - period_end DATE, - is_open_ended BOOLEAN NOT NULL DEFAULT FALSE, -- 无固定结束日期 - - -- 委托方(负责经纪人) - agent_id UUID REFERENCES staff(id) ON DELETE SET NULL, - agent_snapshot JSONB, - - -- 签约方式(选择后动态展示委托书模板) - signing_method VARCHAR(50), - - -- 委托人(产权人)信息 - owner_type VARCHAR(20) NOT NULL DEFAULT 'owner' - CHECK (owner_type IN ('owner','authorized_third')), - -- 从 property_contacts 中选择 - property_owner_contact_id UUID REFERENCES property_contacts(id) ON DELETE SET NULL, - owner_name VARCHAR(50), -- 产权人姓名 - owner_id_type VARCHAR(20), -- 证件类型:身份证/护照 等 - owner_id_number VARCHAR(50), -- 证件号码(加密存储) - owner_id_number_enc BYTEA, - - remarks TEXT, -- 备注,最多200字 - - -- 状态 - status VARCHAR(20) NOT NULL DEFAULT 'active' - CHECK (status IN ('active','expired','cancelled')), - - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - created_by UUID REFERENCES staff(id) ON DELETE SET NULL -); - -CREATE INDEX idx_commissions_property ON commissions(property_id); -CREATE INDEX idx_commissions_active ON commissions(property_id) - WHERE status = 'active'; - --- 委托附件(身份证/房产证/委托书 等) -CREATE TABLE commission_attachments ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - commission_id UUID NOT NULL REFERENCES commissions(id) ON DELETE CASCADE, - category VARCHAR(20) NOT NULL - CHECK (category IN ('id_card','property_cert', - 'commission_letter','other')), - file_key TEXT NOT NULL, - file_name VARCHAR(255) NOT NULL, - file_size INTEGER, - sort_order SMALLINT NOT NULL DEFAULT 0, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE INDEX idx_commission_attachments_commission ON commission_attachments(commission_id); -``` - ---- - -### 3.10 实勘管理(Field Survey) - -```sql -CREATE TABLE field_surveys ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, - - status VARCHAR(10) NOT NULL DEFAULT 'draft' - CHECK (status IN ('draft','submitted')), - - -- GPS 定位 - gps_latitude NUMERIC(10,7), - gps_longitude NUMERIC(10,7), - gps_accuracy NUMERIC(6,2), -- 精度(米) - - description TEXT, -- 实勘说明,最多200字 - - submitted_at TIMESTAMPTZ, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - created_by UUID NOT NULL REFERENCES staff(id) ON DELETE RESTRICT -); - -CREATE INDEX idx_field_surveys_property ON field_surveys(property_id); -CREATE INDEX idx_field_surveys_submitted ON field_surveys(property_id) - WHERE status = 'submitted'; - --- 实勘照片(按空间分类) -CREATE TABLE survey_photos ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - survey_id UUID NOT NULL REFERENCES field_surveys(id) ON DELETE CASCADE, - category VARCHAR(20) NOT NULL - CHECK (category IN ('layout','living_room','dining_room', - 'bedroom','bathroom','kitchen', - 'entrance','balcony','study', - 'indoor_other','outdoor')), - file_key TEXT NOT NULL, -- R2/S3 路径 - thumbnail_key TEXT, -- 缩略图路径 - file_size INTEGER, - width INTEGER, - height INTEGER, - sort_order SMALLINT NOT NULL DEFAULT 0, - is_vr_screenshot BOOLEAN NOT NULL DEFAULT FALSE, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE INDEX idx_survey_photos_survey ON survey_photos(survey_id); -CREATE INDEX idx_survey_photos_category ON survey_photos(survey_id, category); -``` - ---- - -### 3.11 房源图片管理(Property Photos) - -```sql --- ============================================================ --- 房源图片:与实勘照片分离存储,经纪人自主上传和管理 --- 封面限1张,全景类型单独处理 --- ============================================================ - -CREATE TABLE property_photos ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, - - category VARCHAR(20) NOT NULL - CHECK (category IN ('cover','entrance','living_room', - 'dining_room','bedroom','bathroom', - 'kitchen','balcony','study', - 'indoor_other','outdoor','panorama')), - - file_key TEXT NOT NULL, -- R2/S3 原图路径 - thumbnail_key TEXT, -- 缩略图路径(Cloudflare Images 生成) - file_name VARCHAR(255), - file_size INTEGER, - width INTEGER, - height INTEGER, - - is_cover BOOLEAN NOT NULL DEFAULT FALSE, - sort_order SMALLINT NOT NULL DEFAULT 0, - - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - created_by UUID REFERENCES staff(id) ON DELETE SET NULL -); - -CREATE INDEX idx_property_photos_property ON property_photos(property_id); -CREATE INDEX idx_property_photos_cover ON property_photos(property_id) - WHERE is_cover = TRUE; -CREATE INDEX idx_property_photos_category ON property_photos(property_id, category); - --- 唯一约束:每个房源只能有一张封面 -CREATE UNIQUE INDEX idx_property_photos_unique_cover - ON property_photos(property_id) - WHERE is_cover = TRUE; -``` - ---- - -### 3.12 房源附件(Property Attachments) - -```sql -CREATE TABLE property_attachments ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, - - category VARCHAR(20) NOT NULL DEFAULT 'other' - CHECK (category IN ('id_card','property_cert', - 'commission_letter','other')), - - file_key TEXT NOT NULL, - file_name VARCHAR(255) NOT NULL, - file_size INTEGER NOT NULL, - file_type VARCHAR(50), -- MIME type - - sort_order SMALLINT NOT NULL DEFAULT 0, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - created_by UUID REFERENCES staff(id) ON DELETE SET NULL -); - -CREATE INDEX idx_property_attachments_property ON property_attachments(property_id); -CREATE INDEX idx_property_attachments_category ON property_attachments(property_id, category); -``` - ---- - -### 3.13 房源营销信息(Property Marketing) - -```sql -CREATE TABLE property_marketing ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - property_id UUID NOT NULL UNIQUE REFERENCES properties(id) ON DELETE CASCADE, - - marketing_title VARCHAR(30), -- 营销标题 0-30字 - core_selling_points TEXT, -- 核心卖点,最多200字 - owner_attitude TEXT, -- 业主心态,最多200字 - layout_description TEXT, -- 户型介绍,最多200字 - complex_description TEXT, -- 小区介绍,最多200字 - - -- AI 生成标记 - ai_generated_points BOOLEAN NOT NULL DEFAULT FALSE, - ai_generated_attitude BOOLEAN NOT NULL DEFAULT FALSE, - - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_by UUID REFERENCES staff(id) ON DELETE SET NULL -); -``` - ---- - -### 3.14 产证信息(Property Certificate) - -```sql -CREATE TABLE property_certificates ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - property_id UUID NOT NULL UNIQUE REFERENCES properties(id) ON DELETE CASCADE, - - owner_name VARCHAR(100), - owner_id_number VARCHAR(50), -- 身份证号/统一社会信用代码 - owner_cert_type VARCHAR(20), -- 身份证/护照/营业执照 - property_location VARCHAR(500), -- 房屋坐落(产权证书上的地址),最多50字 - - -- 产证状态 - cert_status VARCHAR(30), - cert_no VARCHAR(100), -- 产证号 - first_registered_at DATE, -- 首次登记时间 - ownership_nature VARCHAR(30), - land_nature VARCHAR(30), -- 土地性质 - - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_by UUID REFERENCES staff(id) ON DELETE SET NULL -); -``` - ---- - -### 3.15 楼盘基本信息扩展(Complex Property Info) - -```sql --- 补充:楼盘与房源通过 complex_id 关联,楼盘信息首次填写后修改需走楼盘管理系统 --- 楼盘价格走势(用于楼盘详情页展示) -CREATE TABLE complex_price_trends ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - complex_id UUID NOT NULL REFERENCES complexes(id) ON DELETE CASCADE, - record_month DATE NOT NULL, -- 月份(取该月1日存储) - avg_sale_price NUMERIC(10,2), -- 月均售价(万元/套) - avg_unit_price NUMERIC(10,2), -- 月均单价(元/m²) - transaction_count INTEGER, -- 成交套数 - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE UNIQUE INDEX idx_complex_price_trend_month - ON complex_price_trends(complex_id, record_month); -``` - ---- - -### 3.16 维护完成度评分(Completeness Scoring) - -```sql --- ============================================================ --- 维护完成度:不直接存完整计算明细(减少宽表), --- 以触发器/Celery 任务异步更新 properties.completeness_score --- 此表存储各维度的得分快照,供详情页展示 --- ============================================================ - -CREATE TABLE property_completeness ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - property_id UUID NOT NULL UNIQUE REFERENCES properties(id) ON DELETE CASCADE, - - -- 各维度得分(满分见 PRD 8.2) - score_core_info SMALLINT NOT NULL DEFAULT 0, -- 重点信息 满分8 - score_attachment SMALLINT NOT NULL DEFAULT 0, -- 附件 满分8 - score_survey SMALLINT NOT NULL DEFAULT 0, -- 实勘 满分16 - score_vr SMALLINT NOT NULL DEFAULT 0, -- VR 满分8 - score_key SMALLINT NOT NULL DEFAULT 0, -- 钥匙 满分10 - score_commission SMALLINT NOT NULL DEFAULT 0, -- 委托 满分10 - score_verification SMALLINT NOT NULL DEFAULT 0, -- 验证 满分7 - score_follow_up SMALLINT NOT NULL DEFAULT 0, -- 跟进 满分8 - score_viewing SMALLINT NOT NULL DEFAULT 0, -- 带看 满分8 - score_other SMALLINT NOT NULL DEFAULT 0, -- 其他 满分7 - total_score SMALLINT NOT NULL DEFAULT 0, -- 总分 0-100 - - calculated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); -``` +### 3.3 房源模块(Property Management) + +> **详细模型** → 见 [`DATA_MODEL_PROPERTY.md`](./DATA_MODEL_PROPERTY.md) +> 本节仅作概览,开发时以 DATA_MODEL_PROPERTY.md 为权威定义。 + +**核心表概览**(开发时以 DATA_MODEL_PROPERTY.md 为准): + +| 表名 | 说明 | 关键字段 | +|------|------|----------| +| `properties` | 房源主表(系统核心,89,000+ 数据量) | `status`, `attribute`, `property_type`, `complex_id`, `sale_price`, `area`, `grade`, `completeness_score`, `search_vector` | +| `property_contacts` | 业主/联系人(手机号 AES 加密+哈希索引) | `property_id`, `phone_enc`, `phone_hash`, `identity`, `is_number_holder` | +| `listing_histories` | 挂牌历史快照(不可删除) | `property_id`, `listing_type`, `status`, `sale_price`, `seller_agent_snapshot` | +| `price_changes` | 调价记录(不可删除) | `property_id`, `old_sale_price`, `new_sale_price`, `change_reason`, `changed_by` | +| `follow_logs` | 跟进日志(6种类型,最高写入频率) | `property_id`, `log_type`, `content`, `is_deletable`, `operator_id` | +| `follow_log_attachments` | 跟进附件(图片) | `follow_log_id`, `file_key`, `file_type` | +| `follow_log_recordings` | 跟进录音 | `follow_log_id`, `file_key`, `duration_seconds` | +| `property_keys` | 钥匙管理(机械钥匙/密码) | `property_id`, `key_type`, `holder_id`, `is_active` | +| `key_attachments` | 钥匙附件 | `key_id`, `file_key` | +| `commissions` | 委托管理(独家/非独家) | `property_id`, `commission_type`, `period_start`, `status` | +| `commission_attachments` | 委托附件(身份证/产证/委托书) | `commission_id`, `category`, `file_key` | +| `field_surveys` | 实勘管理(GPS 打卡) | `property_id`, `status`, `gps_latitude`, `gps_longitude`, `created_by` | +| `survey_photos` | 实勘照片(按空间分类) | `survey_id`, `category`, `file_key`, `is_vr_screenshot` | +| `property_photos` | 房源图片(经纪人管理,封面唯一约束) | `property_id`, `category`, `is_cover`, `file_key` | +| `property_attachments` | 房源附件 | `property_id`, `category`, `file_key` | +| `property_marketing` | 营销信息(1:1,卖点/业主心态/介绍) | `property_id`, `marketing_title`, `core_selling_points` | +| `property_certificates` | 产证信息(1:1) | `property_id`, `cert_no`, `owner_name`, `land_nature` | +| `property_completeness` | 维护完成度快照(1:1,Celery 异步计算) | `property_id`, `total_score`, `score_survey`, `score_commission`, ... | +| `property_tags` | 标签字典(系统预置+运营自定义) | `name`, `color`, `is_system` | +| `property_tag_relations` | 房源↔标签多对多 | `property_id`, `tag_id` | +| `property_favorites` | 经纪人收藏房源 | `staff_id`, `property_id` | +| `property_protections` | 保护房设置(1:1) | `property_id`, `is_protected`, `start_at`, `end_at` | +| `number_holder_approvals` | 号码方变更审批 | `property_id`, `applicant_id`, `status` | + +**关键约束提示**: +- `property_contacts.phone_hash` 是重复房源检测的主要依据,录入前必须查重 +- `listing_histories` / `price_changes` **无 deleted_at**,不可删除 +- `follow_logs` 中 `is_deletable=FALSE`(`sensitive_view` 类型)不可软删 +- `completeness_score` 只由 Celery 任务写入,Application 层禁止直接更新 +- `last_followed_at` 由触发器 `trg_update_last_followed` 自动维护 +- `property_photos.is_cover` 唯一约束:每套房源仅一张封面 --- diff --git a/Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md b/Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md new file mode 100644 index 00000000..1d7ac7e0 --- /dev/null +++ b/Project/fonrey/DATA_MODEL/DATA_MODEL_PROPERTY.md @@ -0,0 +1,1168 @@ +# Fonrey — 房源模块数据模型(DATA_MODEL_PROPERTY) + +> **权威定义**:本文件是房源模块所有表结构的唯一权威来源。 +> **主文档引用**:`DATA_MODEL.md` §3.3–§3.16 为本文件的概览摘要,开发以本文件为准。 +> **版本**:v1.0 | **日期**:2026-04-24 + +--- + +## 目录 + +1. [模块说明](#1-模块说明) +2. [表清单](#2-表清单) +3. [枚举值总览](#3-枚举值总览) +4. [DDL 定义](#4-ddl-定义) + - 4.1 [properties(房源主表)](#41-properties房源主表) + - 4.2 [property_contacts(联系人)](#42-property_contacts联系人) + - 4.3 [listing_histories(挂牌历史)](#43-listing_histories挂牌历史) + - 4.4 [price_changes(调价记录)](#44-price_changes调价记录) + - 4.5 [follow_logs(跟进日志)](#45-follow_logs跟进日志) + - 4.6 [follow_log_attachments(跟进附件)](#46-follow_log_attachments跟进附件) + - 4.7 [follow_log_recordings(跟进录音)](#47-follow_log_recordings跟进录音) + - 4.8 [property_keys(钥匙管理)](#48-property_keys钥匙管理) + - 4.9 [key_attachments(钥匙附件)](#49-key_attachments钥匙附件) + - 4.10 [commissions(委托管理)](#410-commissions委托管理) + - 4.11 [commission_attachments(委托附件)](#411-commission_attachments委托附件) + - 4.12 [field_surveys(实勘管理)](#412-field_surveys实勘管理) + - 4.13 [survey_photos(实勘照片)](#413-survey_photos实勘照片) + - 4.14 [property_photos(房源图片)](#414-property_photos房源图片) + - 4.15 [property_attachments(房源附件)](#415-property_attachments房源附件) + - 4.16 [property_marketing(营销信息)](#416-property_marketing营销信息) + - 4.17 [property_certificates(产证信息)](#417-property_certificates产证信息) + - 4.18 [property_completeness(维护完成度)](#418-property_completeness维护完成度) + - 4.19 [property_tags / property_tag_relations(标签)](#419-property_tags--property_tag_relations标签) + - 4.20 [property_favorites(收藏)](#420-property_favorites收藏) + - 4.21 [property_protections(保护房)](#421-property_protections保护房) + - 4.22 [number_holder_approvals(号码方审批)](#422-number_holder_approvals号码方审批) +5. [触发器](#5-触发器) +6. [查询模式参考](#6-查询模式参考) +7. [禁止操作](#7-禁止操作) + +--- + +## 1 模块说明 + +**房源(Property)** 是 Fonrey 系统的核心领域对象,代表一套二手房源的完整档案。 + +核心业务规则: + +| 规则 | 说明 | +|------|------| +| 多态交易状态 | 一套房源可同时处于出售、出租或租售三态(`status`) | +| 流通属性 | 公盘/私盘/特盘/封盘(`attribute`),控制可见范围 | +| 相关方体系 | 首录方/号码方/出售方/实买方,通过 `staff_id` 关联 | +| 联系人加密 | 业主/联系人手机号 AES-256-GCM 加密,SHA-256 哈希索引 | +| 跟进日志不可删 | `sensitive_view` 类型跟进 `is_deletable=FALSE`,合规强制 | +| 挂牌历史不可删 | `listing_histories` 无 `deleted_at`,append-only | +| 调价记录不可删 | `price_changes` 无 `deleted_at`,append-only | +| 完成度异步计算 | `completeness_score` 由 Celery 任务更新,不实时 | + +--- + +## 2 表清单 + +| # | 表名 | 说明 | 是否可删除 | +|---|------|------|----------| +| 1 | `properties` | 房源主表 | 软删除(`deleted_at`) | +| 2 | `property_contacts` | 业主/联系人(手机号加密) | 软删除 | +| 3 | `listing_histories` | 挂牌历史快照 | **不可删除** | +| 4 | `price_changes` | 调价记录 | **不可删除** | +| 5 | `follow_logs` | 跟进日志(6种类型) | 部分不可删(`sensitive_view`) | +| 6 | `follow_log_attachments` | 跟进附件(图片) | 随日志联级 | +| 7 | `follow_log_recordings` | 跟进录音 | 随日志联级 | +| 8 | `property_keys` | 钥匙管理 | 软删除(`is_active=FALSE`) | +| 9 | `key_attachments` | 钥匙附件 | 随钥匙联级 | +| 10 | `commissions` | 委托管理 | 状态驱动(`status='cancelled'`) | +| 11 | `commission_attachments` | 委托附件 | 随委托联级 | +| 12 | `field_surveys` | 实勘管理 | 软删除 | +| 13 | `survey_photos` | 实勘照片(按空间分类) | 随实勘联级 | +| 14 | `property_photos` | 房源图片(经纪人管理) | 软删除 | +| 15 | `property_attachments` | 房源附件 | 直接删除 | +| 16 | `property_marketing` | 营销信息(1:1) | 随房源联级 | +| 17 | `property_certificates` | 产证信息(1:1) | 随房源联级 | +| 18 | `property_completeness` | 维护完成度快照(1:1) | 随房源联级 | +| 19 | `property_tags` | 标签字典 | 系统标签不可删 | +| 20 | `property_tag_relations` | 房源↔标签多对多 | 随房源/标签联级 | +| 21 | `property_favorites` | 经纪人收藏房源 | 直接删除 | +| 22 | `property_protections` | 保护房设置(1:1) | 随房源联级 | +| 23 | `number_holder_approvals` | 号码方变更审批 | 随房源联级 | + +--- + +## 3 枚举值总览 + +### property_type(房源类型) + +| 值 | 说明 | +|----|------| +| `residential` | 住宅 | +| `villa` | 别墅 | +| `commercial_residential` | 商住 | +| `shop` | 商铺 | +| `office` | 写字楼 | +| `other` | 其他 | + +### status(交易状态) + +| 值 | 说明 | +|----|------| +| `for_sale` | 出售 | +| `for_rent` | 出租 | +| `for_sale_rent` | 租售 | +| `suspended` | 暂缓 | +| `sold_elsewhere` | 他售 | +| `rented_elsewhere` | 他租 | +| `sold` | 成交 | +| `unlisted` | 未挂牌 | + +### attribute(流通属性) + +| 值 | 说明 | +|----|------| +| `public` | 公盘 | +| `private` | 私盘 | +| `special` | 特盘 | +| `sealed` | 封盘 | + +### orientation(朝向) + +| 值 | 说明 | +|----|------| +| `east` | 东 | +| `south` | 南 | +| `west` | 西 | +| `north` | 北 | +| `southeast` | 东南 | +| `northeast` | 东北 | +| `east_west` | 东西 | +| `south_north` | 南北 | +| `northwest` | 西北 | +| `southwest` | 西南 | + +### decoration(装修情况) + +| 值 | 说明 | +|----|------| +| `rough` | 毛坯 | +| `plain` | 清水 | +| `simple` | 简装 | +| `medium` | 中装 | +| `fine` | 精装 | +| `luxury` | 豪装 | + +### grade(等级) + +| 值 | 说明 | +|----|------| +| `A_urgent` | A(急迫) | +| `A` | A | +| `B` | B(较强) | +| `C` | C(一般) | +| `D` | D | + +### follow_log.log_type(跟进日志类型) + +| 值 | 说明 | 可删除 | +|----|------|--------| +| `written` | 经纪人主动写入跟进 | 是 | +| `modified` | 字段变更自动生成 | 是 | +| `sensitive_op` | 敏感信息操作跟进 | 否 | +| `sensitive_view` | 敏感信息查看(查看号码等) | **否** | +| `other` | 其他(钥匙/新增联系人等) | 是 | +| `system` | 系统日志 | 是 | + +--- + +## 4 DDL 定义 + +### 4.1 properties(房源主表) + +```sql +-- ============================================================ +-- 房源主表:系统最核心的表,全部筛选/排序/搜索围绕此表展开 +-- 设计重点:89,000+ 数据量,复合索引策略,分区预留 +-- ============================================================ + +CREATE TABLE properties ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- ── 基础分类 ── + property_type VARCHAR(20) NOT NULL + CHECK (property_type IN ('residential','villa','commercial_residential', + 'shop','office','other')), + + -- ── 交易状态 ── + status VARCHAR(20) NOT NULL DEFAULT 'for_sale' + CHECK (status IN ('for_sale','for_rent','for_sale_rent', + 'suspended','sold_elsewhere','rented_elsewhere', + 'sold','unlisted')), + + -- ── 流通属性 ── + attribute VARCHAR(20) NOT NULL DEFAULT 'public' + CHECK (attribute IN ('public','private','special','sealed')), + private_reason TEXT, -- 私盘/封盘必填说明 + + -- ── 位置信息 ── + complex_id UUID NOT NULL REFERENCES complexes(id) ON DELETE RESTRICT, + building_id UUID REFERENCES buildings(id) ON DELETE SET NULL, + block_no VARCHAR(30), -- 栋/幢/弄号 + unit_no VARCHAR(30), -- 单元号 + room_no VARCHAR(30), -- 房号/门牌号 + floor SMALLINT NOT NULL, -- 所在楼层 + total_floors SMALLINT NOT NULL, -- 总楼层 + CONSTRAINT chk_floor CHECK (floor > 0 AND floor <= total_floors), + + -- ── 户型 ── + bedroom_count SMALLINT NOT NULL DEFAULT 0, -- 室 + living_room_count SMALLINT NOT NULL DEFAULT 0, -- 厅 + bathroom_count SMALLINT NOT NULL DEFAULT 0, -- 卫 + kitchen_count SMALLINT NOT NULL DEFAULT 0, -- 厨 + balcony_count SMALLINT NOT NULL DEFAULT 0, -- 阳台数 + + -- ── 面积 ── + area NUMERIC(8,2) NOT NULL, -- 建筑面积 m² + inner_area NUMERIC(8,2), -- 套内面积 m² + + -- ── 价格 ── + sale_price NUMERIC(12,2), -- 挂牌售价(万元) + sale_bottom_price NUMERIC(12,2), -- 售底价(万元,内部可见) + sale_record_price NUMERIC(12,2), -- 备案/核验价(万元) + rent_price NUMERIC(10,2), -- 挂牌租价(元/月) + + -- ── 基础物理属性 ── + orientation VARCHAR(10) + CHECK (orientation IN ('east','south','west','north', + 'southeast','northeast','east_west', + 'south_north','northwest','southwest')), + decoration VARCHAR(10) + CHECK (decoration IN ('rough','plain','simple','medium', + 'fine','luxury')), + has_elevator BOOLEAN, + built_year SMALLINT, + + -- ── 用途 ── + usage_type VARCHAR(30), -- 住宅/商住/商业/普通住宅/花园洋房 等 + usage_subtype VARCHAR(30), -- 细分用途 + + -- ── 商铺专属 ── + shop_frontage NUMERIC(6,2), -- 开间(米) + shop_depth NUMERIC(6,2), -- 进深(米) + shop_height NUMERIC(6,2), -- 层高(米) + shop_location VARCHAR(20) + CHECK (shop_location IS NULL OR + shop_location IN ('street','mall','residential', + 'ground_floor','complex')), + + -- ── 房屋状态 ── + house_status VARCHAR(20) + CHECK (house_status IN ('owner_occupied','vacant', + 'tenant_occupied','unknown')), + viewing_time VARCHAR(20) + CHECK (viewing_time IN ('anytime','by_appointment','inconvenient')), + + -- ── 等级与标签 ── + grade VARCHAR(10) + CHECK (grade IN ('A_urgent','A','B','C','D')), + + -- ── 交易属性 ── + ownership_years VARCHAR(30), -- 房本年限:不满2年/满2年/满5年 等 + ownership_years_detail VARCHAR(20), -- 满五/不满五 + ownership_nature VARCHAR(20) + CHECK (ownership_nature IS NULL OR + ownership_nature IN ('commercial','reform_housing', + 'collective','economic')), + is_only_house BOOLEAN, -- 唯一住房 + payment_method VARCHAR(30) + CHECK (payment_method IS NULL OR + payment_method IN ('full','mortgage','installment','advance')), + tax_included VARCHAR(10) + CHECK (tax_included IS NULL OR + tax_included IN ('each_party','net','inclusive')), + has_mortgage BOOLEAN, + has_loan BOOLEAN, + has_seal BOOLEAN, + has_restriction BOOLEAN, + original_price NUMERIC(12,2), -- 原购价(万元) + sale_reason TEXT, -- 售房原因(最多200字) + + -- ── 营销备注 ── + remarks TEXT, -- 房源备注(最多500字) + + -- ── 相关方 ── + first_recorder_id UUID REFERENCES staff(id) ON DELETE SET NULL, -- 首录方 + number_holder_id UUID REFERENCES staff(id) ON DELETE SET NULL, -- 号码方 + seller_agent_id UUID REFERENCES staff(id) ON DELETE SET NULL, -- 出售方 + buyer_agent_id UUID REFERENCES staff(id) ON DELETE SET NULL, -- 实买方 + + -- ── 来源 ── + source VARCHAR(50), -- 房源来源渠道(lookup_items 维护) + + -- ── 维护完成度(冗余缓存,Celery 定期重算)── + completeness_score SMALLINT NOT NULL DEFAULT 0, -- 0-100 + + -- ── 时间轨迹 ── + listed_at TIMESTAMPTZ, -- 最近一次挂牌时间 + last_followed_at TIMESTAMPTZ, -- 最后跟进时间(冗余,加速排序) + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + created_by UUID REFERENCES staff(id) ON DELETE SET NULL, + updated_by UUID REFERENCES staff(id) ON DELETE SET NULL, + + -- ── 全文检索向量 ── + search_vector TSVECTOR +); + +-- ── 索引策略 ── + +-- 核心列表过滤:status + attribute + type +CREATE INDEX idx_properties_status_attr ON properties(status, attribute, property_type) + WHERE deleted_at IS NULL; + +-- 区域筛选 +CREATE INDEX idx_properties_complex ON properties(complex_id) + WHERE deleted_at IS NULL; + +-- 售价排序 +CREATE INDEX idx_properties_sale_price ON properties(sale_price DESC NULLS LAST) + WHERE deleted_at IS NULL AND status IN ('for_sale','for_sale_rent'); + +-- 面积区间 +CREATE INDEX idx_properties_area ON properties(area) + WHERE deleted_at IS NULL; + +-- 挂牌时间倒序 +CREATE INDEX idx_properties_listed_at ON properties(listed_at DESC NULLS LAST) + WHERE deleted_at IS NULL; + +-- 超时跟进检测 +CREATE INDEX idx_properties_last_followed ON properties(last_followed_at DESC NULLS LAST) + WHERE deleted_at IS NULL; + +-- 户型筛选 +CREATE INDEX idx_properties_bedroom ON properties(bedroom_count) + WHERE deleted_at IS NULL; + +-- 等级筛选 +CREATE INDEX idx_properties_grade ON properties(grade) + WHERE deleted_at IS NULL; + +-- 完成度排序 +CREATE INDEX idx_properties_completeness ON properties(completeness_score) + WHERE deleted_at IS NULL; + +-- 全文搜索 +CREATE INDEX idx_properties_search ON properties USING gin(search_vector); + +-- 相关方快速定位 +CREATE INDEX idx_properties_seller_agent ON properties(seller_agent_id) + WHERE deleted_at IS NULL; +CREATE INDEX idx_properties_number_holder ON properties(number_holder_id) + WHERE deleted_at IS NULL; + +-- 列表默认排序(status + listed_at) +CREATE INDEX idx_properties_list_default ON properties(status, listed_at DESC NULLS LAST) + WHERE deleted_at IS NULL; + +-- 高频复合索引(status + attribute + complex_id + sale_price) +CREATE INDEX idx_properties_list_composite ON properties + (status, attribute, complex_id, sale_price DESC NULLS LAST) + WHERE deleted_at IS NULL; + +-- 个人仪表板(与我相关) +CREATE INDEX idx_properties_my_properties ON properties + (seller_agent_id, status, listed_at DESC NULLS LAST) + WHERE deleted_at IS NULL; +``` + +--- + +### 4.2 property_contacts(联系人) + +```sql +-- ============================================================ +-- 业主/联系人:手机号加密存储,哈希值支持重复检测 +-- 安全要点:任何查看明文号码的行为均触发审计日志 +-- ============================================================ + +CREATE TABLE property_contacts ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + + name VARCHAR(50) NOT NULL, + gender VARCHAR(10) NOT NULL DEFAULT 'male' + CHECK (gender IN ('male','female')), + identity VARCHAR(20) NOT NULL DEFAULT 'contact' + CHECK (identity IN ('owner','contact','subletter', + 'tenant','agent','corporate')), + -- owner=业主, contact=联系人, subletter=二房东, tenant=租客, + -- agent=代理人, corporate=企业法人 + + -- 手机号:加密存储 + 哈希索引 + phone_enc BYTEA NOT NULL, -- AES-256-GCM 加密 + phone_hash VARCHAR(64) NOT NULL, -- SHA-256(phone),去重查询 + phone2_enc BYTEA, + phone2_hash VARCHAR(64), + + wechat VARCHAR(100), + qq VARCHAR(20), + remarks TEXT, + + -- 号码方标记(关联审批流) + is_number_holder BOOLEAN NOT NULL DEFAULT FALSE, + number_holder_approved_at TIMESTAMPTZ, + + sort_order INTEGER NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ, + created_by UUID REFERENCES staff(id) ON DELETE SET NULL, + updated_by UUID REFERENCES staff(id) ON DELETE SET NULL +); + +CREATE INDEX idx_contacts_property ON property_contacts(property_id) + WHERE deleted_at IS NULL; + +-- 关键:手机号哈希全局索引(重复房源检测) +CREATE INDEX idx_contacts_phone_hash ON property_contacts(phone_hash) + WHERE deleted_at IS NULL; +CREATE INDEX idx_contacts_phone2_hash ON property_contacts(phone2_hash) + WHERE phone2_hash IS NOT NULL AND deleted_at IS NULL; +``` + +--- + +### 4.3 listing_histories(挂牌历史) + +```sql +-- ============================================================ +-- 挂牌历史:记录房源每次上架的完整快照 +-- 注意:无 deleted_at,不可删除(append-only,合规要求) +-- ============================================================ + +CREATE TABLE listing_histories ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE RESTRICT, + + listing_type VARCHAR(20) NOT NULL + CHECK (listing_type IN ('for_sale','for_rent')), + status VARCHAR(20) NOT NULL DEFAULT 'active' + CHECK (status IN ('active','ended')), + + -- 价格快照 + sale_price NUMERIC(12,2), + rent_price NUMERIC(10,2), + sale_unit_price NUMERIC(10,2), -- 元/m²,计算字段 + + -- 交易信息快照 + ownership_years VARCHAR(30), + is_only_house BOOLEAN, + tax_included VARCHAR(10), + sale_reason TEXT, + + -- 经纪人快照(防止人员变动后丢失历史数据) + seller_agent_id UUID REFERENCES staff(id) ON DELETE SET NULL, + seller_agent_snapshot JSONB, -- {name, store_group, org_unit_name} + + started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + ended_at TIMESTAMPTZ, + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + -- 无 deleted_at:此表记录不可删除 +); + +CREATE INDEX idx_listing_histories_property ON listing_histories(property_id); +CREATE INDEX idx_listing_histories_active ON listing_histories(property_id) + WHERE status = 'active'; +``` + +--- + +### 4.4 price_changes(调价记录) + +```sql +-- ============================================================ +-- 调价记录:支持折线图展示,不可删除(append-only) +-- ============================================================ + +CREATE TABLE price_changes ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE RESTRICT, + + old_sale_price NUMERIC(12,2), + new_sale_price NUMERIC(12,2), + old_bottom_price NUMERIC(12,2), + new_bottom_price NUMERIC(12,2), + old_record_price NUMERIC(12,2), + new_record_price NUMERIC(12,2), + old_rent_price NUMERIC(10,2), + new_rent_price NUMERIC(10,2), + + change_reason TEXT NOT NULL, -- 最多200字 + changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + changed_by UUID NOT NULL REFERENCES staff(id) ON DELETE RESTRICT + -- 无 deleted_at:不可删除 +); + +CREATE INDEX idx_price_changes_property ON price_changes(property_id); +CREATE INDEX idx_price_changes_time ON price_changes(property_id, changed_at DESC); +``` + +--- + +### 4.5 follow_logs(跟进日志) + +```sql +-- ============================================================ +-- 跟进日志:系统最高写入频率的表 +-- 6 种类型:written / modified / sensitive_op / sensitive_view / other / system +-- sensitive_view 类型:is_deletable=FALSE,合规不可删 +-- ============================================================ + +CREATE TABLE follow_logs ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + + log_type VARCHAR(30) NOT NULL + CHECK (log_type IN ('written','modified','sensitive_op', + 'sensitive_view','other','system')), + + -- 写入跟进专用字段 + purpose VARCHAR(50), -- 跟进目的(lookup_items 维护) + content TEXT, -- 最少6字,最多500字 + + -- AI 辅助判断标签 + ai_tag VARCHAR(20) + CHECK (ai_tag IS NULL OR ai_tag IN ('ai_for_sale','ai_not_for_sale')), + + -- 修改跟进专用字段 + change_detail JSONB, + -- 格式:{"field": "sale_price", "old": 850, "new": 800, "label": "售价"} + + -- 系统显示标签 + log_tag VARCHAR(50), + -- 如:查看号码/图片下载/改状态/改价格/改等级/修改相关方 + + -- 可见性控制 + is_public BOOLEAN NOT NULL DEFAULT TRUE, + -- FALSE = 仅本人及上级可见 + + -- 操作人 + operator_id UUID REFERENCES staff(id) ON DELETE SET NULL, + operator_snapshot JSONB, -- {name, role, org_unit_name, store_group} + + -- 是否可删除(sensitive_view = FALSE,合规强制) + is_deletable BOOLEAN NOT NULL DEFAULT TRUE, + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ -- 仅 is_deletable=TRUE 时可软删 +); + +-- 时间线展示(核心) +CREATE INDEX idx_follow_logs_property_time ON follow_logs(property_id, created_at DESC) + WHERE deleted_at IS NULL; + +-- 6个 Tab 过滤 +CREATE INDEX idx_follow_logs_type ON follow_logs(property_id, log_type, created_at DESC) + WHERE deleted_at IS NULL; + +-- 操作员过滤 +CREATE INDEX idx_follow_logs_operator ON follow_logs(operator_id, created_at DESC) + WHERE deleted_at IS NULL; + +-- 合规审计(敏感类型专用) +CREATE INDEX idx_follow_logs_sensitive ON follow_logs(property_id, created_at DESC) + WHERE log_type IN ('sensitive_view','sensitive_op'); +``` + +--- + +### 4.6 follow_log_attachments(跟进附件) + +```sql +CREATE TABLE follow_log_attachments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + follow_log_id UUID NOT NULL REFERENCES follow_logs(id) ON DELETE CASCADE, + + file_key TEXT NOT NULL, -- Cloudflare R2 存储路径 + file_name VARCHAR(255) NOT NULL, + file_size INTEGER NOT NULL, -- bytes + file_type VARCHAR(10) + CHECK (file_type IN ('bmp','jpg','png','svg','gif')), + sort_order SMALLINT NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_follow_attachments_log ON follow_log_attachments(follow_log_id); +``` + +--- + +### 4.7 follow_log_recordings(跟进录音) + +```sql +CREATE TABLE follow_log_recordings ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + follow_log_id UUID NOT NULL REFERENCES follow_logs(id) ON DELETE CASCADE, + + file_key TEXT NOT NULL, -- Cloudflare R2 存储路径 + duration_seconds INTEGER, -- 录音时长(秒) + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_follow_recordings_log ON follow_log_recordings(follow_log_id); +``` + +--- + +### 4.8 property_keys(钥匙管理) + +```sql +CREATE TABLE property_keys ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + + key_type VARCHAR(20) NOT NULL + CHECK (key_type IN ('mechanical','password')), + + -- 钥匙持有方 + holder_id UUID REFERENCES staff(id) ON DELETE SET NULL, + holder_snapshot JSONB, -- {name, store_group} + storage_unit_id UUID REFERENCES org_units(id) ON DELETE SET NULL, -- 保管部门 + + -- 他司钥匙标记 + is_other_agency BOOLEAN NOT NULL DEFAULT FALSE, + other_agency_info VARCHAR(30), -- 最多30字 + + remarks TEXT, -- 最多200字 + + is_active BOOLEAN NOT NULL DEFAULT TRUE, -- FALSE=已归还/失效 + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_by UUID REFERENCES staff(id) ON DELETE SET NULL +); + +CREATE INDEX idx_property_keys_property ON property_keys(property_id) + WHERE is_active = TRUE; +``` + +--- + +### 4.9 key_attachments(钥匙附件) + +```sql +CREATE TABLE key_attachments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + key_id UUID NOT NULL REFERENCES property_keys(id) ON DELETE CASCADE, + + file_key TEXT NOT NULL, + file_name VARCHAR(255) NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_key_attachments_key ON key_attachments(key_id); +``` + +--- + +### 4.10 commissions(委托管理) + +```sql +CREATE TABLE commissions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + + commission_type VARCHAR(50) NOT NULL, -- 独家委托/非独家委托(lookup_items 维护) + period_start DATE NOT NULL, + period_end DATE, + is_open_ended BOOLEAN NOT NULL DEFAULT FALSE, -- 无固定结束日期 + + -- 委托经纪人 + agent_id UUID REFERENCES staff(id) ON DELETE SET NULL, + agent_snapshot JSONB, -- {name, store_group} + + signing_method VARCHAR(50), -- 签约方式(选择后动态展示委托书模板) + + -- 委托人(产权人)信息 + owner_type VARCHAR(20) NOT NULL DEFAULT 'owner' + CHECK (owner_type IN ('owner','authorized_third')), + property_owner_contact_id UUID REFERENCES property_contacts(id) ON DELETE SET NULL, + owner_name VARCHAR(50), + owner_id_type VARCHAR(20), -- 身份证/护照 等 + owner_id_number VARCHAR(50), -- 证件号(明文,仅供参考) + owner_id_number_enc BYTEA, -- 证件号 AES-256-GCM 加密 + + remarks TEXT, -- 最多200字 + + status VARCHAR(20) NOT NULL DEFAULT 'active' + CHECK (status IN ('active','expired','cancelled')), + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_by UUID REFERENCES staff(id) ON DELETE SET NULL +); + +CREATE INDEX idx_commissions_property ON commissions(property_id); +CREATE INDEX idx_commissions_active ON commissions(property_id) + WHERE status = 'active'; +``` + +--- + +### 4.11 commission_attachments(委托附件) + +```sql +CREATE TABLE commission_attachments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + commission_id UUID NOT NULL REFERENCES commissions(id) ON DELETE CASCADE, + + category VARCHAR(20) NOT NULL + CHECK (category IN ('id_card','property_cert', + 'commission_letter','other')), + file_key TEXT NOT NULL, + file_name VARCHAR(255) NOT NULL, + file_size INTEGER, + sort_order SMALLINT NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_commission_attachments_commission ON commission_attachments(commission_id); +``` + +--- + +### 4.12 field_surveys(实勘管理) + +```sql +CREATE TABLE field_surveys ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + + status VARCHAR(10) NOT NULL DEFAULT 'draft' + CHECK (status IN ('draft','submitted')), + + -- GPS 定位(实勘打卡) + gps_latitude NUMERIC(10,7), + gps_longitude NUMERIC(10,7), + gps_accuracy NUMERIC(6,2), -- 精度(米) + + description TEXT, -- 实勘说明,最多200字 + + submitted_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_by UUID NOT NULL REFERENCES staff(id) ON DELETE RESTRICT +); + +CREATE INDEX idx_field_surveys_property ON field_surveys(property_id); +CREATE INDEX idx_field_surveys_submitted ON field_surveys(property_id) + WHERE status = 'submitted'; +``` + +--- + +### 4.13 survey_photos(实勘照片) + +```sql +CREATE TABLE survey_photos ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + survey_id UUID NOT NULL REFERENCES field_surveys(id) ON DELETE CASCADE, + + category VARCHAR(20) NOT NULL + CHECK (category IN ('layout','living_room','dining_room', + 'bedroom','bathroom','kitchen', + 'entrance','balcony','study', + 'indoor_other','outdoor')), + file_key TEXT NOT NULL, -- Cloudflare R2 路径 + thumbnail_key TEXT, -- 缩略图路径 + file_size INTEGER, + width INTEGER, + height INTEGER, + sort_order SMALLINT NOT NULL DEFAULT 0, + is_vr_screenshot BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_survey_photos_survey ON survey_photos(survey_id); +CREATE INDEX idx_survey_photos_category ON survey_photos(survey_id, category); +``` + +--- + +### 4.14 property_photos(房源图片) + +```sql +-- ============================================================ +-- 房源图片:经纪人自主上传和管理,与实勘照片分离存储 +-- 封面限1张(唯一约束),全景类型单独处理 +-- ============================================================ + +CREATE TABLE property_photos ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + + category VARCHAR(20) NOT NULL + CHECK (category IN ('cover','entrance','living_room', + 'dining_room','bedroom','bathroom', + 'kitchen','balcony','study', + 'indoor_other','outdoor','panorama')), + + file_key TEXT NOT NULL, -- R2/S3 原图路径 + thumbnail_key TEXT, -- Cloudflare Images 生成的缩略图 + file_name VARCHAR(255), + file_size INTEGER, + width INTEGER, + height INTEGER, + + is_cover BOOLEAN NOT NULL DEFAULT FALSE, + sort_order SMALLINT NOT NULL DEFAULT 0, + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_by UUID REFERENCES staff(id) ON DELETE SET NULL +); + +CREATE INDEX idx_property_photos_property ON property_photos(property_id); +CREATE INDEX idx_property_photos_cover ON property_photos(property_id) + WHERE is_cover = TRUE; +CREATE INDEX idx_property_photos_category ON property_photos(property_id, category); + +-- 每套房源只能有一张封面 +CREATE UNIQUE INDEX idx_property_photos_unique_cover + ON property_photos(property_id) + WHERE is_cover = TRUE; +``` + +--- + +### 4.15 property_attachments(房源附件) + +```sql +CREATE TABLE property_attachments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + + category VARCHAR(20) NOT NULL DEFAULT 'other' + CHECK (category IN ('id_card','property_cert', + 'commission_letter','other')), + + file_key TEXT NOT NULL, + file_name VARCHAR(255) NOT NULL, + file_size INTEGER NOT NULL, + file_type VARCHAR(50), -- MIME type + + sort_order SMALLINT NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_by UUID REFERENCES staff(id) ON DELETE SET NULL +); + +CREATE INDEX idx_property_attachments_property ON property_attachments(property_id); +CREATE INDEX idx_property_attachments_category ON property_attachments(property_id, category); +``` + +--- + +### 4.16 property_marketing(营销信息) + +```sql +-- 1:1 扩展表,营销标题/核心卖点/业主心态/户型介绍/小区介绍 +CREATE TABLE property_marketing ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL UNIQUE REFERENCES properties(id) ON DELETE CASCADE, + + marketing_title VARCHAR(30), -- 营销标题,0-30字 + core_selling_points TEXT, -- 核心卖点,最多200字 + owner_attitude TEXT, -- 业主心态,最多200字 + layout_description TEXT, -- 户型介绍,最多200字 + complex_description TEXT, -- 小区介绍,最多200字 + + -- AI 生成标记 + ai_generated_points BOOLEAN NOT NULL DEFAULT FALSE, + ai_generated_attitude BOOLEAN NOT NULL DEFAULT FALSE, + + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_by UUID REFERENCES staff(id) ON DELETE SET NULL +); +``` + +--- + +### 4.17 property_certificates(产证信息) + +```sql +-- 1:1 扩展表,存储产权证相关信息 +CREATE TABLE property_certificates ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL UNIQUE REFERENCES properties(id) ON DELETE CASCADE, + + owner_name VARCHAR(100), + owner_id_number VARCHAR(50), -- 身份证号/统一社会信用代码 + owner_cert_type VARCHAR(20), -- 身份证/护照/营业执照 + property_location VARCHAR(500), -- 房屋坐落(产权证书上的地址),最多50字 + + cert_status VARCHAR(30), -- 产证状态 + cert_no VARCHAR(100), -- 产证号 + first_registered_at DATE, -- 首次登记时间 + ownership_nature VARCHAR(30), -- 权属性质 + land_nature VARCHAR(30), -- 土地性质 + + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_by UUID REFERENCES staff(id) ON DELETE SET NULL +); +``` + +--- + +### 4.18 property_completeness(维护完成度) + +```sql +-- ============================================================ +-- 维护完成度快照(1:1) +-- 各维度得分由 Celery 任务异步计算后写入,非实时 +-- properties.completeness_score 为冗余总分,供列表排序用 +-- ============================================================ + +CREATE TABLE property_completeness ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL UNIQUE REFERENCES properties(id) ON DELETE CASCADE, + + score_core_info SMALLINT NOT NULL DEFAULT 0, -- 重点信息 满分8 + score_attachment SMALLINT NOT NULL DEFAULT 0, -- 附件 满分8 + score_survey SMALLINT NOT NULL DEFAULT 0, -- 实勘 满分16 + score_vr SMALLINT NOT NULL DEFAULT 0, -- VR 满分8 + score_key SMALLINT NOT NULL DEFAULT 0, -- 钥匙 满分10 + score_commission SMALLINT NOT NULL DEFAULT 0, -- 委托 满分10 + score_verification SMALLINT NOT NULL DEFAULT 0, -- 验证 满分7 + score_follow_up SMALLINT NOT NULL DEFAULT 0, -- 跟进 满分8 + score_viewing SMALLINT NOT NULL DEFAULT 0, -- 带看 满分8 + score_other SMALLINT NOT NULL DEFAULT 0, -- 其他 满分7 + total_score SMALLINT NOT NULL DEFAULT 0, -- 总分 0-100 + + calculated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +--- + +### 4.19 property_tags / property_tag_relations(标签) + +```sql +-- 标签字典(系统预置 + 运营自定义) +CREATE TABLE property_tags ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name VARCHAR(50) NOT NULL, + color VARCHAR(7), -- HEX 颜色,如 #FF5733 + is_system BOOLEAN NOT NULL DEFAULT FALSE, -- 系统预置标签不可删除 + sort_order INTEGER NOT NULL DEFAULT 0, + is_active BOOLEAN NOT NULL DEFAULT TRUE +); + +-- 房源 ↔ 标签 多对多 +CREATE TABLE property_tag_relations ( + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + tag_id UUID NOT NULL REFERENCES property_tags(id) ON DELETE CASCADE, + PRIMARY KEY (property_id, tag_id) +); + +CREATE INDEX idx_property_tags_property ON property_tag_relations(property_id); +CREATE INDEX idx_property_tags_tag ON property_tag_relations(tag_id); +``` + +--- + +### 4.20 property_favorites(收藏) + +```sql +-- 经纪人收藏房源(快速访问) +CREATE TABLE property_favorites ( + staff_id UUID NOT NULL REFERENCES staff(id) ON DELETE CASCADE, + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + PRIMARY KEY (staff_id, property_id) +); + +CREATE INDEX idx_property_favorites_staff ON property_favorites(staff_id); +``` + +--- + +### 4.21 property_protections(保护房) + +```sql +-- 1:1,标记某套房源是否受保护(防止被他人抢单/公盘化) +CREATE TABLE property_protections ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL UNIQUE REFERENCES properties(id) ON DELETE CASCADE, + + is_protected BOOLEAN NOT NULL DEFAULT FALSE, + reason TEXT, + start_at TIMESTAMPTZ, + end_at TIMESTAMPTZ, + set_by UUID REFERENCES staff(id) ON DELETE SET NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +--- + +### 4.22 number_holder_approvals(号码方审批) + +```sql +-- 号码方变更审批流:经纪人申请,上级审批 +CREATE TABLE number_holder_approvals ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + property_id UUID NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + contact_id UUID NOT NULL REFERENCES property_contacts(id) ON DELETE CASCADE, + + applicant_id UUID NOT NULL REFERENCES staff(id) ON DELETE RESTRICT, + approver_id UUID REFERENCES staff(id) ON DELETE SET NULL, + + status VARCHAR(20) NOT NULL DEFAULT 'pending' + CHECK (status IN ('pending','approved','rejected')), + remarks TEXT, + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + decided_at TIMESTAMPTZ +); + +CREATE INDEX idx_number_holder_approvals_status ON number_holder_approvals(status) + WHERE status = 'pending'; +CREATE INDEX idx_number_holder_approvals_property ON number_holder_approvals(property_id); +``` + +--- + +## 5 触发器 + +### 5.1 房源全文搜索向量自动维护 + +```sql +CREATE OR REPLACE FUNCTION update_property_search_vector() +RETURNS TRIGGER AS $$ +BEGIN + NEW.search_vector := + setweight(to_tsvector('simple', COALESCE(NEW.block_no, '') || + ' ' || COALESCE(NEW.unit_no, '') || + ' ' || COALESCE(NEW.room_no, '')), 'A') || + setweight(to_tsvector('simple', COALESCE(NEW.remarks, '')), 'C'); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_property_search_vector + BEFORE INSERT OR UPDATE OF block_no, unit_no, room_no, remarks + ON properties + FOR EACH ROW EXECUTE FUNCTION update_property_search_vector(); +``` + +### 5.2 last_followed_at 自动维护 + +```sql +-- 写入跟进日志时,自动更新 properties.last_followed_at(加速超时检测排序) +CREATE OR REPLACE FUNCTION update_property_last_followed() +RETURNS TRIGGER AS $$ +BEGIN + IF NEW.log_type = 'written' THEN + UPDATE properties + SET last_followed_at = NEW.created_at, + updated_at = NOW() + WHERE id = NEW.property_id; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_update_last_followed + AFTER INSERT ON follow_logs + FOR EACH ROW EXECUTE FUNCTION update_property_last_followed(); +``` + +--- + +## 6 查询模式参考 + +### 6.1 房源列表页(高频) + +```sql +-- 出售公盘,按挂牌时间倒序 +SELECT p.id, p.status, p.attribute, p.sale_price, p.area, + p.bedroom_count, p.floor, p.total_floors, p.completeness_score, + p.listed_at +FROM properties p +WHERE p.status = 'for_sale' + AND p.attribute = 'public' + AND p.deleted_at IS NULL +ORDER BY p.listed_at DESC NULLS LAST +LIMIT 20 OFFSET 0; +-- 命中索引:idx_properties_list_default +``` + +### 6.2 区域筛选(楼盘 + 商圈下钻) + +```sql +-- 某商圈下所有出售房源 +SELECT p.* +FROM properties p +JOIN complexes c ON c.id = p.complex_id +JOIN complex_business_areas cba ON cba.complex_id = c.id +WHERE cba.business_area_id = :business_area_id + AND p.status IN ('for_sale','for_sale_rent') + AND p.deleted_at IS NULL +ORDER BY p.listed_at DESC NULLS LAST; +-- 命中索引:idx_properties_complex + complex_business_areas 索引 +``` + +### 6.3 与我相关(经纪人仪表板) + +```sql +SELECT * FROM properties +WHERE seller_agent_id = :my_staff_id + AND status NOT IN ('sold','unlisted') + AND deleted_at IS NULL +ORDER BY listed_at DESC NULLS LAST; +-- 命中索引:idx_properties_my_properties +``` + +### 6.4 重复房源检测(录入前必查) + +```sql +-- 通过联系人手机号哈希检测 +SELECT pc.property_id, p.status, p.deleted_at +FROM property_contacts pc +JOIN properties p ON p.id = pc.property_id +WHERE pc.phone_hash = SHA256(:input_phone) + AND pc.deleted_at IS NULL; +-- 命中索引:idx_contacts_phone_hash +``` + +### 6.5 跟进日志时间线(详情页) + +```sql +-- 房源详情页跟进 Tab(全部类型,时间倒序) +SELECT fl.*, fla.file_key, fla.file_name +FROM follow_logs fl +LEFT JOIN follow_log_attachments fla ON fla.follow_log_id = fl.id +WHERE fl.property_id = :property_id + AND fl.deleted_at IS NULL +ORDER BY fl.created_at DESC; +-- 命中索引:idx_follow_logs_property_time +``` + +--- + +## 7 禁止操作 + +| 操作 | 影响表 | 原因 | +|------|--------|------| +| `DELETE FROM listing_histories` | `listing_histories` | 挂牌历史不可删除,合规审计要求 | +| `DELETE FROM price_changes` | `price_changes` | 调价记录不可删除 | +| `UPDATE follow_logs SET deleted_at = NOW() WHERE is_deletable = FALSE` | `follow_logs` | 敏感信息查看类型日志不可删除 | +| 直接修改 `properties.completeness_score` | `properties` | 完成度分数只由 Celery 任务计算更新,禁止 Application 层直接写入 | +| 直接修改 `properties.last_followed_at` | `properties` | 由触发器 `trg_update_last_followed` 自动维护 | +| 删除 `property_tags WHERE is_system = TRUE` | `property_tags` | 系统预置标签不可删除 | +| 明文存储手机号 | `property_contacts` | 手机号必须 AES-256-GCM 加密后存 `phone_enc`,同时更新 `phone_hash` | + +--- + +*DATA_MODEL_PROPERTY.md — Fonrey 房产经纪管理系统房源模块数据模型 v1.0* +*下一步:API 接口规范(房源模块 URL 设计 + Request/Response Schema)* diff --git a/Project/fonrey/DATA_MODEL/diagram/fonrey-er.drawio b/Project/fonrey/DATA_MODEL/diagram/fonrey-er.drawio new file mode 100644 index 00000000..d259a57c --- /dev/null +++ b/Project/fonrey/DATA_MODEL/diagram/fonrey-er.drawio @@ -0,0 +1,860 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Project/fonrey/DATA_MODEL/fonrey-data-model.xml b/Project/fonrey/DATA_MODEL/fonrey-data-model.xml index 90f81918..221ada31 100644 --- a/Project/fonrey/DATA_MODEL/fonrey-data-model.xml +++ b/Project/fonrey/DATA_MODEL/fonrey-data-model.xml @@ -1,774 +1,368 @@ - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/Project/fonrey/DATA_MODEL/fonrey-data-model.xml.bak b/Project/fonrey/DATA_MODEL/fonrey-data-model.xml.bak new file mode 100644 index 00000000..64e2e512 --- /dev/null +++ b/Project/fonrey/DATA_MODEL/fonrey-data-model.xml.bak @@ -0,0 +1,774 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/wiki/index.md b/wiki/index.md index 48dd659f..7c0d8cae 100644 --- a/wiki/index.md +++ b/wiki/index.md @@ -4,6 +4,7 @@ - [Overview](overview.md) — living synthesis ## Sources +- [2026-04-24] [CTP Topic 56 Automated Infrastructure Testing](sources/ctp-topic-56-automated-infrastructure-testing.md) - [2026-04-24] [Public Cloud Learning Sessions - Ollie Workflow and The Demand Process - 20240416](sources/public-cloud-learning-sessions-ollie-workflow-and-the-demand-process-20240416-16.md) - [2026-04-24] [CTP Topic 33 An Introduction to GitOps](sources/ctp-topic-33-an-introduction-to-gitops.md) - [2026-04-24] [CTP Topic 3 Deploy and maintain infrastructure](sources/ctp-topic-3-deploy-and-maintain-infrastructure.md) @@ -413,7 +414,6 @@ - [2026-04-19] [public-cloud-learning-sessions-reducing-cloud-costs-20250318-170100-meeting-reco](sources/public-cloud-learning-sessions-reducing-cloud-costs-20250318-170100-meeting-reco.md) — (expected: wiki/sources/public-cloud-learning-sessions-reducing-cloud-costs-20250318-170100-meeting-reco.md — source missing) - [2026-04-19] [ctp-topic-13-cloud-finops-micro-focus-policies-best-practices-to-optimize-the-co](sources/ctp-topic-13-cloud-finops-micro-focus-policies-best-practices-to-optimize-the-co.md) — (expected: wiki/sources/ctp-topic-13-cloud-finops-micro-focus-policies-best-practices-to-optimize-the-co.md — source missing) - [2026-04-19] [ctp-topic-15-working-with-renovatebot](sources/ctp-topic-15-working-with-renovatebot.md) — (expected: wiki/sources/ctp-topic-15-working-with-renovatebot.md — source missing) -- [2026-04-19] [ctp-topic-56-automated-infrastructure-testing](sources/ctp-topic-56-automated-infrastructure-testing.md) — (expected: wiki/sources/ctp-topic-56-automated-infrastructure-testing.md — source missing) - [Your-AI-Isn-t-Stupid---It-Just-Needs-a-Better-Harness--Lychee-Technology-Engineering-Blog](sources/Your-AI-Isn-t-Stupid---It-Just-Needs-a-Better-Harness--Lychee-Technology-Engineering-Blog.md) — (expected: wiki/sources/Your-AI-Isn-t-Stupid---It-Just-Needs-a-Better-Harness--Lychee-Technology-Engineering-Blog.md — source missing) - [Expose-hermes-agent-as-an-OpenAI-compatible-API-for-any-frontend](sources/Expose-hermes-agent-as-an-OpenAI-compatible-API-for-any-frontend.md) — (expected: wiki/sources/Expose-hermes-agent-as-an-OpenAI-compatible-API-for-any-frontend.md — source missing) - [zk-steward](sources/zk-steward.md) — (expected: wiki/sources/zk-steward.md — source missing) diff --git a/wiki/log.md b/wiki/log.md index c95ccaf9..82666c90 100644 --- a/wiki/log.md +++ b/wiki/log.md @@ -2389,3 +2389,15 @@ - Key Entities 中提及的 Victor Etkin 仅出现 1 次,不满足 ≥2 次条件,以 wikilink 形式记录于 Source page - Key Concepts 中 Kubernetes/Atlantis 已有 wikilink 指向其他 Source page - 冲突检测:与 ctp-topic-39(Atlantis 不支持 EKS)存在 Atlantis + Kubernetes 实践约束差异,已记录于 Source page Contradictions + +## [2026-04-24] ingest | CTP Topic 56 Automated Infrastructure Testing +- Source file: Cloud & DevOps/Public-Cloud-Learning-Sessions/06_CI_CD_GitOps/ctp-topic-56-automated-infrastructure-testing.md +- Status: ✅ 成功摄入 +- Summary: Mark Francis 主讲自动化基础设施测试,倡导将 TerraTest(Golang 框架)应用于 Terraform IaC 的 apply → test → destroy 自动化验证循环;核心主张集成测试超越语法检查,TDD 应用于 IaC 领域,测试作为首要开发步骤;价值观:"让机器做重复的事,把人脑留给复杂的人类问题" +- Concepts identified: [[Infrastructure Testing]], [[TerraTest]], [[Test-Driven Development (TDD)]], [[IaC Testing Framework]] +- Source page: wiki/sources/ctp-topic-56-automated-infrastructure-testing.md +- Notes: + - index.md 更新:新增条目于 CTP Topic 33 (GitOps) 之后 + - overview.md 更新:新增条目于 Cloud Transformation & DevOps 章节,GitOps 和 CI/CD Pipeline 质量保障层 + - Key Entities 中 Mark Francis 仅出现 1 次,以 wikilink 形式记录于 Source page + - 冲突检测:待发现相关冲突内容,Contradictions 暂置空占位 diff --git a/wiki/overview.md b/wiki/overview.md index 34f067a9..990065a0 100644 --- a/wiki/overview.md +++ b/wiki/overview.md @@ -49,7 +49,9 @@ Cloud Transformation Programme (CTP) materials cover AWS landing zones, EKS, Ter **[[ctp-topic-33-an-introduction-to-gitops]]**(CTP Topic 33):Victor Etkin 讲解 GitOps 方法论入门——GitOps 将软件开发原则应用于部署流程,解决部署失败和配置不一致问题。四大原则:声明式配置、版本控制、CD 流程分离、自修复协调器;核心工具仅需 Git。GitOps Controller 持续比对 Git 声明的期望状态与系统实际状态,自动调和偏差。Pull 模型(代理同时监控 Git 和目标系统)比 Push 模型安全性更高,是 GitOps 推荐模式。CI 专注代码构建和分析,CD 专注二进制部署,两者解耦增强安全性。幂等平台(如 Kubernetes)是 CD 流程顺利运行的必要条件。Git 提交日志天然构成合规审计追踪。属 [[GitOps]] 概念层核心来源,与 [[ctp-topic-32-using-atlantis-cicd-for-infrastructure-deployments]](Atlantis 工具)和 [[ctp-topic-2-git]](Git 基础)共同构成 CI/CD/GitOps 完整知识链路。 -**[[ctp-topic-21-supply-chain-security-in-micro-focus]]**(CTP Topic 21):Micro Focus 产品安全小组 Shlomi Ben-Hur 主讲的软件供应链安全新方法——核心议题:在云转型背景下,软件供应链安全已成为企业安全战略的重中之重。供应链(产品层面)涵盖源码管理(SCM)、构建组件(CI)、制品库到最终交付系统(CD)的所有环节,Micro Focus 内部存在 17 种不同 SCM 工具的极高多样性。主要驱动因素:SolarWinds 攻击事件(通过渗透构建过程注入恶意代码)、美国网络安全行政命令、以及向 AWS/SaaS 迁移带来的开放性风险。核心转变:从过去 99% 关注研发安全(代码扫描/渗透测试)转向全生命周期安全防护;供应链安全成为 SDL(安全开发生命周期)的第五大支柱,强调必须同时确保 CI 过程(构建环境/自动化服务器)和 CD 过程(交付系统)的完整性,防止黑客在任何环节篡改二进制文件。属 [[Supply Chain Security(供应链安全)]] 在 [[Micro Focus]] 云转型场景的核心实践,与 [[DevSecOps]](开发安全运维一体化)高度关联。 +**[[ctp-topic-56-automated-infrastructure-testing]]**(CTP Topic 56):Mark Francis 主讲自动化基础设施测试——将软件测试原则应用于 Terraform IaC 代码,通过 TerraTest(Golang 框架)实现 apply → test → destroy 自动化验证循环。核心主张:集成测试超越语法检查,验证实际部署行为是否符合预期;倡导测试驱动开发(TDD)应用于 IaC 领域,先写测试再实现功能;提议将测试编写作为基础设施开发的首要步骤,移除手动验证,追求自动化验证套件和更高的部署信心。核心价值观:"让机器做重复的事,把人脑留给复杂的人类问题"。属 [[GitOps]] 和 [[CI/CD Pipeline]] 的质量保障层,与 [[ctp-topic-33-an-introduction-to-gitops]](GitOps 概念)和 [[ctp-topic-32-using-atlantis-cicd-for-infrastructure-deployments]](Atlantis 工具)共同构成完整的 IaC 质量保障链路。 + +**[[ctp-topic-21-supply-chain-security-in-micro-focus]]**(CTP Topic 21): **[[ctp-topic-24-micro-focus-product-privacy-framework]]**(CTP Topic 24):Micro Focus 产品隐私框架在云转型中的应用——PSAC(产品安全顾问委员会)与法律顾问合作,将 GDPR/CCPA 等晦涩法律条款翻译为约 110 项低级别技术要求;隐私框架是 STLC(安全开发生命周期)中 13 个安全与隐私轨道之一;通过五类需求(架构类/文档类/法律类/实现类/SAS 运营类)和成熟度模型(0-4 级)评估产品隐私合规状态;通过"蜘蛛图"直观展示产品在安全去标识化、被遗忘权、数据可移植性等 KPI 上的合规现状;最终产出标准化《产品隐私设置文档》,确保客户获得一致的隐私信息参考。属 [[Product Privacy Framework(产品隐私框架)]] 在 [[Micro Focus]] 云转型场景的核心实践,与 [[Micro Focus Security Development Life Cycle (STLC) Overview]](STLC 整体架构)直接关联。 diff --git a/wiki/sources/ctp-topic-56-automated-infrastructure-testing.md b/wiki/sources/ctp-topic-56-automated-infrastructure-testing.md new file mode 100644 index 00000000..443f2d06 --- /dev/null +++ b/wiki/sources/ctp-topic-56-automated-infrastructure-testing.md @@ -0,0 +1,58 @@ +--- +title: "CTP Topic 56 Automated Infrastructure Testing" +type: source +tags: + - Testing + - IaC + - Automation + - CTP + - Terraform + - TerraTest + - TDD +sources: [] +last_updated: 2026-04-14 +--- + +## Source File +- [[Cloud & DevOps/Public-Cloud-Learning-Sessions/06_CI_CD_GitOps/ctp-topic-56-automated-infrastructure-testing.md]] + +## Summary(用中文描述) +- 核心主题:自动化基础设施测试——将软件测试原则应用于 Terraform IaC 代码,通过 TerraTest 框架实现基础设施的 Apply → Test → Destroy 自动化验证循环。 +- 问题域:传统 Terraform 验证仅做语法检查,无法验证实际部署后的行为是否符合预期;手动测试耗时且不可重复;缺乏测试的基础设施代码变更信心不足。 +- 方法/机制: + - TerraTest(Golang 库):自动执行 apply → test → destroy 生命周期,输出结构化测试结果 + - 测试驱动开发(TDD):先写测试,再实现功能,确保测试先行且全面覆盖 + - 提议的新工作流:将测试编写作为基础设施开发的首要步骤,移除手动验证环节 +- 结论/价值:自动化测试虽然前期投入时间,但长期回报是减少 Bug、提升部署信心、积累可重复的测试套件;"让机器做重复的事,把人脑留给复杂的人类问题" + +## Key Claims(用中文描述) +- 集成测试对于验证已部署基础设施的功能至关重要,超越了语法检查,确保实际部署与预期相符。 +- TerraTest 通过自动化 apply-test-destroy 循环简化了测试流程,降低了基础设施测试的门槛。 +- 测试驱动开发(TDD)在基础设施即代码领域的应用:先写测试,再实现功能,聚焦开发并积累全面测试套件。 +- 提议的工作流将测试编写作为核心步骤,移除手动验证,追求自动化验证套件和更高的部署信心。 +- 长期收益(减少 Bug、提升信心)远超前期投入困难;测试应被视为一等公民。 + +## Key Quotes +> "I think the bottom quote, just I think let's leave the repetitive things for the computers to do and use our brains for the complex human things." +> — Mark Francis,核心价值观:重复性工作交给机器,人脑专注于复杂的人类问题 + +> "I'm just extending the value of putting stuff as code." +> — Mark Francis,将测试代码化的价值延伸 + +## Key Concepts +- [[Infrastructure Testing(基础设施测试)]]:对 Terraform 等 IaC 工具部署的实际基础设施资源进行验证,而非仅检查语法或计划输出 +- [[TerraTest]]:HashiCorp 官方出品的 Golang 基础设施测试框架,支持 apply-test-destroy 自动化循环 +- [[Test-Driven Development(TDD)]]:先写测试用例,再实现功能,确保测试覆盖全面且聚焦开发过程 +- [[IaC Testing Framework]]:专门针对基础设施即代码的测试工具链,包括语法检查、计划验证、集成测试等多个层次 + +## Key Entities +- [[Mark Francis]]:CTP Topic 56 讲师,主讲自动化基础设施测试实践 + +## Connections +- [[ctp-topic-33-an-introduction-to-gitops]] ← extends ← [[ctp-topic-56-automated-infrastructure-testing.md]] +- [[ctp-topic-9-ci-cd-with-gruntwork]] ← depends_on ← [[ctp-topic-56-automated-infrastructure-testing.md]] +- [[ctp-topic-3-deploy-and-maintain-infrastructure]] ← extends ← [[ctp-topic-56-automated-infrastructure-testing.md]] +- [[ctp-topic-32-using-atlantis-cicd-for-infrastructure-deployments]] ← related_to ← [[ctp-topic-56-automated-infrastructure-testing.md]] + +## Contradictions +- (待发现:如有相关页面引用与本页面观点冲突的内容,将在此记录)