# Invoice Master - 数据库 Schema 设计 **版本**: v3.0 **数据库**: PostgreSQL 15+ **ORM**: Entity Framework Core 8 **日期**: 2026-02-03 --- ## 1. ER 图 (多会计系统版) ``` ┌──────────────────┐ ┌──────────────────────────┐ ┌──────────────────┐ │ users │ │ accounting_connections │ │ supplier_cache │ ├──────────────────┤ ├──────────────────────────┤ ├──────────────────┤ │ id (PK) │◄──────┤ id (PK) │◄──────┤ id (PK) │ │ email │ │ user_id (FK) │ │ connection_id(FK)│ │ hashed_password │ │ provider (e.g. 'fortnox')│ │ supplier_number │ │ is_active │ │ access_token_encrypted │ │ name │ │ created_at │ │ refresh_token_encrypted │ │ org_number │ └──────────────────┘ │ company_name │ │ cached_at │ │ company_org_number │ └──────────────────┘ │ UNIQUE(user_id, provider)│ └───────────┬──────────────┘ │ │ ┌───────────▼───────────┐ │ invoices │ ├───────────────────────┤ │ id (PK) │ │ connection_id (FK) │◄──────────────────────┐ │ provider │ │ │ file_path │ │ │ extraction_data │ │ │ supplier_number │ │ │ voucher_number │ │ │ status │ │ └───────────┬───────────┘ │ │ │ │ │ ┌───────────▼───────────┐ ┌───────────────▼──┐ │ invoice_reviews │ │ processing_queue │ ├───────────────────────┤ ├──────────────────┤ │ id (PK) │ │ id (PK) │ │ invoice_id (FK) │ │ invoice_id (FK) │ │ reviewed_by │ │ status │ │ corrections │ │ retry_count │ │ approved │ │ error_message │ └───────────────────────┘ └──────────────────┘ ``` --- ## 2. 表结构定义 ### 2.1 users - 用户表 存储系统用户基本信息。 ```sql CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email VARCHAR(255) UNIQUE NOT NULL, hashed_password VARCHAR(255) NOT NULL, full_name VARCHAR(255), is_active BOOLEAN DEFAULT true, is_superuser BOOLEAN DEFAULT false, -- 时间戳 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), last_login_at TIMESTAMP WITH TIME ZONE ); -- 索引 CREATE INDEX idx_users_email ON users(email); CREATE INDEX idx_users_active ON users(is_active); ``` ### 2.2 accounting_connections - 会计系统连接表 存储多个会计系统的 OAuth 连接信息,一个用户可以连接多个会计系统。 ```sql CREATE TABLE accounting_connections ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, -- 会计系统标识 provider VARCHAR(50) NOT NULL, -- 'fortnox', 'visma', 'hogia', etc. -- OAuth Tokens (加密存储) access_token_encrypted TEXT NOT NULL, refresh_token_encrypted TEXT NOT NULL, expires_at TIMESTAMP WITH TIME ZONE NOT NULL, scope TEXT, -- 权限范围,不同 provider 格式不同 -- 公司信息 company_name VARCHAR(255), company_org_number VARCHAR(20), -- 默认设置 (各 provider 可能有不同的默认值) default_voucher_series VARCHAR(10) DEFAULT 'A', default_account_code INTEGER DEFAULT 5460, auto_attach_pdf BOOLEAN DEFAULT true, auto_create_supplier BOOLEAN DEFAULT false, -- 状态 is_active BOOLEAN DEFAULT true, last_sync_at TIMESTAMP WITH TIME ZONE, -- 时间戳 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), -- 一个用户可连接多个会计系统,但不能重复连接同一个 UNIQUE(user_id, provider) ); -- 索引 CREATE INDEX idx_connections_user ON accounting_connections(user_id); CREATE INDEX idx_connections_provider ON accounting_connections(provider); CREATE INDEX idx_connections_active ON accounting_connections(is_active); CREATE INDEX idx_connections_expires ON accounting_connections(expires_at); ``` ### 2.3 invoices - 发票处理记录表 存储发票处理历史和状态,支持多会计系统。 ```sql CREATE TYPE invoice_status AS ENUM ( 'pending', -- 等待处理 'uploading', -- 正在上传 'processing', -- OCR 处理中 'preview', -- 等待用户确认 'importing', -- 正在导入会计系统 'imported', -- 已成功导入 'failed' -- 处理失败 ); CREATE TYPE supplier_match_action AS ENUM ( 'USE_EXISTING', -- 使用现有供应商 'CREATE_NEW', -- 创建新供应商 'SUGGEST_MATCH' -- 建议匹配 ); CREATE TABLE invoices ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), connection_id UUID NOT NULL REFERENCES accounting_connections(id) ON DELETE CASCADE, -- 冗余存储 provider,便于查询 provider VARCHAR(50) NOT NULL, -- 文件信息 original_filename VARCHAR(255) NOT NULL, storage_path TEXT NOT NULL, file_size INTEGER, file_hash VARCHAR(64), -- SHA-256 用于去重 -- OCR 提取结果 (JSONB 存储结构化数据) extraction_data JSONB, extraction_confidence DECIMAL(3,2), -- 0.00 - 1.00 -- 提取的字段 (冗余存储便于查询) extracted_supplier_name VARCHAR(255), extracted_supplier_org_number VARCHAR(20), extracted_invoice_number VARCHAR(100), extracted_invoice_date DATE, extracted_due_date DATE, extracted_amount_total DECIMAL(12,2), extracted_amount_vat DECIMAL(12,2), extracted_vat_rate INTEGER, extracted_ocr_number VARCHAR(50), extracted_bankgiro VARCHAR(50), extracted_plusgiro VARCHAR(50), extracted_currency VARCHAR(3) DEFAULT 'SEK', -- 供应商匹配结果 supplier_number VARCHAR(50), supplier_match_confidence DECIMAL(3,2), supplier_match_action supplier_match_action, -- 会计凭证信息 voucher_series VARCHAR(10), voucher_number VARCHAR(50), voucher_url TEXT, -- 会计科目分配 (JSONB 数组) voucher_rows JSONB, -- 处理状态 status invoice_status DEFAULT 'pending', error_message TEXT, error_code VARCHAR(50), -- 用户审核 reviewed_by UUID REFERENCES users(id), reviewed_at TIMESTAMP WITH TIME ZONE, -- 附件信息 attachment_id VARCHAR(100), attachment_url TEXT, -- 时间戳 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), processed_at TIMESTAMP WITH TIME ZONE ); -- 索引 CREATE INDEX idx_invoices_connection ON invoices(connection_id); CREATE INDEX idx_invoices_provider ON invoices(provider); CREATE INDEX idx_invoices_status ON invoices(status); CREATE INDEX idx_invoices_created ON invoices(created_at DESC); CREATE INDEX idx_invoices_file_hash ON invoices(file_hash); CREATE INDEX idx_invoices_supplier ON invoices(extracted_supplier_org_number); -- GIN 索引用于 JSONB 查询 CREATE INDEX idx_invoices_extraction ON invoices USING GIN (extraction_data); ``` ### 2.4 supplier_cache - 供应商缓存表 缓存各会计系统的供应商列表,减少 API 调用。 ```sql CREATE TABLE supplier_cache ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), connection_id UUID NOT NULL REFERENCES accounting_connections(id) ON DELETE CASCADE, -- 供应商信息 (各 provider 字段可能不同) supplier_number VARCHAR(50) NOT NULL, name VARCHAR(255) NOT NULL, organisation_number VARCHAR(20), address1 VARCHAR(255), address2 VARCHAR(255), postcode VARCHAR(20), city VARCHAR(100), country VARCHAR(100), phone VARCHAR(50), email VARCHAR(255), bankgiro_number VARCHAR(50), plusgiro_number VARCHAR(50), -- 缓存元数据 cached_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), expires_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() + INTERVAL '1 hour', UNIQUE(connection_id, supplier_number) ); -- 索引 CREATE INDEX idx_supplier_cache_connection ON supplier_cache(connection_id); CREATE INDEX idx_supplier_cache_org ON supplier_cache(organisation_number); CREATE INDEX idx_supplier_cache_name ON supplier_cache(name); CREATE INDEX idx_supplier_cache_expires ON supplier_cache(expires_at); ``` ### 2.5 invoice_reviews - 发票审核记录表 存储用户对 OCR 结果的修改和确认。 ```sql CREATE TABLE invoice_reviews ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), invoice_id UUID NOT NULL REFERENCES invoices(id) ON DELETE CASCADE, -- 审核人 reviewed_by UUID NOT NULL REFERENCES users(id), -- 用户修正的数据 (JSONB) corrections JSONB, -- 审核结果 approved BOOLEAN NOT NULL, rejection_reason TEXT, -- 时间戳 reviewed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- 索引 CREATE INDEX idx_reviews_invoice ON invoice_reviews(invoice_id); CREATE INDEX idx_reviews_user ON invoice_reviews(reviewed_by); ``` ### 2.6 processing_queue - 处理队列表 异步任务队列,用于发票处理。 ```sql CREATE TYPE queue_status AS ENUM ( 'queued', 'processing', 'completed', 'failed', 'cancelled' ); CREATE TABLE processing_queue ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), invoice_id UUID NOT NULL REFERENCES invoices(id) ON DELETE CASCADE, -- 任务类型 task_type VARCHAR(50) NOT NULL, -- 'ocr', 'import', 'upload_attachment' -- 状态 status queue_status DEFAULT 'queued', priority INTEGER DEFAULT 5, -- 1-10, 数字越小优先级越高 -- 重试机制 retry_count INTEGER DEFAULT 0, max_retries INTEGER DEFAULT 3, -- 调度 scheduled_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), started_at TIMESTAMP WITH TIME ZONE, completed_at TIMESTAMP WITH TIME ZONE, -- 错误信息 error_message TEXT, error_stack TEXT, -- 处理结果 result_data JSONB, -- 工作节点 worker_id VARCHAR(100) ); -- 索引 CREATE INDEX idx_queue_status ON processing_queue(status); CREATE INDEX idx_queue_scheduled ON processing_queue(scheduled_at); CREATE INDEX idx_queue_invoice ON processing_queue(invoice_id); CREATE INDEX idx_queue_priority ON processing_queue(status, priority, scheduled_at); ``` ### 2.7 account_mappings - 科目映射表 存储用户自定义的会计科目映射规则,支持多会计系统。 ```sql CREATE TABLE account_mappings ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), connection_id UUID NOT NULL REFERENCES accounting_connections(id) ON DELETE CASCADE, -- 会计系统标识 provider VARCHAR(50) NOT NULL, -- 匹配规则 supplier_org_number VARCHAR(20), -- 特定供应商 keyword VARCHAR(100), -- 关键词匹配 -- 科目设置 (不同 provider 可能有不同的科目体系) account_code INTEGER NOT NULL, account_name VARCHAR(255), vat_rate INTEGER DEFAULT 25, -- 描述模板 description_template VARCHAR(255), -- e.g., "{supplier_name} - {invoice_number}" -- 优先级 (数字越大优先级越高) priority INTEGER DEFAULT 0, -- 状态 is_active BOOLEAN DEFAULT true, -- 时间戳 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), -- 约束: supplier_org_number 和 keyword 至少有一个 CONSTRAINT check_mapping_rule CHECK ( supplier_org_number IS NOT NULL OR keyword IS NOT NULL ) ); -- 索引 CREATE INDEX idx_mappings_connection ON account_mappings(connection_id); CREATE INDEX idx_mappings_provider ON account_mappings(provider); CREATE INDEX idx_mappings_supplier ON account_mappings(supplier_org_number); CREATE INDEX idx_mappings_keyword ON account_mappings(keyword); ``` ### 2.8 api_logs - API 日志表 记录关键 API 调用用于审计和调试。 ```sql CREATE TABLE api_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- 请求信息 request_id VARCHAR(100) NOT NULL, connection_id UUID REFERENCES accounting_connections(id), user_id UUID REFERENCES users(id), -- API 详情 provider VARCHAR(50), -- 'fortnox', 'visma', 'ocr', 'internal' endpoint VARCHAR(255) NOT NULL, method VARCHAR(10) NOT NULL, -- 状态 status_code INTEGER, is_success BOOLEAN, -- 性能 request_started_at TIMESTAMP WITH TIME ZONE, request_ended_at TIMESTAMP WITH TIME ZONE, duration_ms INTEGER, -- 错误 error_code VARCHAR(50), error_message TEXT, -- 元数据 (脱敏) metadata JSONB, -- 时间戳 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- 索引 CREATE INDEX idx_logs_request ON api_logs(request_id); CREATE INDEX idx_logs_connection ON api_logs(connection_id); CREATE INDEX idx_logs_provider ON api_logs(provider); CREATE INDEX idx_logs_created ON api_logs(created_at DESC); ``` --- ## 3. 视图 ### 3.1 invoice_summary - 发票摘要视图 ```sql CREATE VIEW invoice_summary AS SELECT i.id, i.connection_id, i.provider, i.original_filename, i.status, i.extraction_confidence, i.extracted_supplier_name, i.extracted_invoice_number, i.extracted_invoice_date, i.extracted_amount_total, i.extracted_currency, i.supplier_number, i.supplier_match_confidence, i.voucher_number, i.voucher_series, i.created_at, i.processed_at, ac.company_name as connection_company, CASE WHEN i.status = 'imported' THEN true ELSE false END as is_imported FROM invoices i JOIN accounting_connections ac ON i.connection_id = ac.id; ``` ### 3.2 connection_stats - 连接统计视图 ```sql CREATE VIEW connection_stats AS SELECT ac.id as connection_id, ac.provider, ac.company_name, ac.is_active, COUNT(i.id) as total_invoices, COUNT(CASE WHEN i.status = 'imported' THEN 1 END) as imported_invoices, COUNT(CASE WHEN i.status = 'failed' THEN 1 END) as failed_invoices, AVG(i.extraction_confidence) as avg_confidence, MAX(i.created_at) as last_invoice_at FROM accounting_connections ac LEFT JOIN invoices i ON ac.id = i.connection_id GROUP BY ac.id, ac.provider, ac.company_name, ac.is_active; ``` ### 3.3 user_connections_view - 用户连接视图 ```sql CREATE VIEW user_connections_view AS SELECT u.id as user_id, u.email, ac.id as connection_id, ac.provider, ac.company_name, ac.company_org_number, ac.is_active as connection_active, ac.created_at as connected_at FROM users u LEFT JOIN accounting_connections ac ON u.id = ac.user_id; ``` --- ## 4. 函数和触发器 ### 4.1 自动更新时间戳 ```sql -- 创建更新函数 CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ language 'plpgsql'; -- 应用到各表 CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_connections_updated_at BEFORE UPDATE ON accounting_connections FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_invoices_updated_at BEFORE UPDATE ON invoices FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_mappings_updated_at BEFORE UPDATE ON account_mappings FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); ``` ### 4.2 发票状态变更日志 ```sql CREATE TABLE invoice_status_history ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), invoice_id UUID NOT NULL REFERENCES invoices(id) ON DELETE CASCADE, old_status invoice_status, new_status invoice_status NOT NULL, changed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), changed_by UUID REFERENCES users(id) ); -- 触发器记录状态变更 CREATE OR REPLACE FUNCTION log_invoice_status_change() RETURNS TRIGGER AS $$ BEGIN IF OLD.status IS DISTINCT FROM NEW.status THEN INSERT INTO invoice_status_history (invoice_id, old_status, new_status) VALUES (NEW.id, OLD.status, NEW.status); END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER invoice_status_change_trigger AFTER UPDATE ON invoices FOR EACH ROW EXECUTE FUNCTION log_invoice_status_change(); ``` ### 4.3 自动设置 provider 字段 ```sql -- 当插入发票时,自动从 connection 获取 provider CREATE OR REPLACE FUNCTION set_invoice_provider() RETURNS TRIGGER AS $$ BEGIN IF NEW.provider IS NULL THEN SELECT provider INTO NEW.provider FROM accounting_connections WHERE id = NEW.connection_id; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER set_invoice_provider_trigger BEFORE INSERT ON invoices FOR EACH ROW EXECUTE FUNCTION set_invoice_provider(); ``` --- ## 5. 初始化数据 ```sql -- 默认会计科目映射 (BAS 2024 - Fortnox) INSERT INTO account_mappings (connection_id, provider, account_code, account_name, vat_rate, description_template, priority) SELECT ac.id, ac.provider, code, name, vat_rate, description_template, priority FROM accounting_connections ac CROSS JOIN (VALUES (2440, 'Leverantörsskulder', 0, 'Faktura {invoice_number}', 0), (2610, 'Ingående moms', 25, 'Moms 25%', 0), (2620, 'Ingående moms', 12, 'Moms 12%', 0), (2630, 'Ingående moms', 6, 'Moms 6%', 0), (5460, 'Kontorsmaterial', 25, 'Kontorsmaterial', 1), (5710, 'Frakter', 25, 'Frakt', 1), (6100, 'Övriga externa tjänster', 25, 'Övriga tjänster', 1), (6210, 'Konsultarvoden', 25, 'Konsult', 2) ) AS defaults(code, name, vat_rate, description_template, priority) WHERE ac.provider = 'fortnox'; ``` --- ## 6. 迁移脚本 ### 6.1 从 v1.0 迁移到 v2.0 ```sql -- 迁移脚本: 从单 Fortnox 支持到多会计系统支持 -- 1. 创建新表 accounting_connections CREATE TABLE accounting_connections ( -- ... (见 2.2 节定义) ); -- 2. 迁移 fortnox_tenants 数据到 accounting_connections INSERT INTO accounting_connections ( id, user_id, provider, access_token_encrypted, refresh_token_encrypted, expires_at, scope, company_name, company_org_number, default_voucher_series, default_account_code, auto_attach_pdf, auto_create_supplier, is_active, last_sync_at, created_at, updated_at ) SELECT id, user_id, 'fortnox', access_token_encrypted, refresh_token_encrypted, expires_at, scope, company_name, company_org_number, default_voucher_series, default_account_code, auto_attach_pdf, auto_create_supplier, is_active, last_sync_at, created_at, updated_at FROM fortnox_tenants; -- 3. 创建新表 invoices CREATE TABLE invoices ( -- ... (见 2.3 节定义) ); -- 4. 迁移 fortnox_invoices 数据到 invoices INSERT INTO invoices ( id, connection_id, provider, original_filename, storage_path, file_size, file_hash, extraction_data, extraction_confidence, extracted_supplier_name, extracted_supplier_org_number, extracted_invoice_number, extracted_invoice_date, extracted_due_date, extracted_amount_total, extracted_amount_vat, extracted_vat_rate, extracted_ocr_number, extracted_bankgiro, extracted_plusgiro, extracted_currency, supplier_number, supplier_match_confidence, supplier_match_action, voucher_series, voucher_number, voucher_url, voucher_rows, status, error_message, error_code, reviewed_by, reviewed_at, attachment_id, attachment_url, created_at, updated_at, processed_at ) SELECT id, tenant_id, 'fortnox', original_filename, storage_path, file_size, file_hash, extraction_data, extraction_confidence, extracted_supplier_name, extracted_supplier_org_number, extracted_invoice_number, extracted_invoice_date, extracted_due_date, extracted_amount_total, extracted_amount_vat, extracted_vat_rate, extracted_ocr_number, extracted_bankgiro, extracted_plusgiro, extracted_currency, supplier_number, supplier_match_confidence, supplier_match_action, voucher_series, voucher_number, voucher_url, voucher_rows, status, error_message, error_code, reviewed_by, reviewed_at, attachment_id, attachment_url, created_at, updated_at, processed_at FROM fortnox_invoices; -- 5. 更新外键引用 -- 更新 supplier_cache ALTER TABLE supplier_cache DROP CONSTRAINT supplier_cache_tenant_id_fkey; ALTER TABLE supplier_cache RENAME COLUMN tenant_id TO connection_id; ALTER TABLE supplier_cache ADD CONSTRAINT supplier_cache_connection_id_fkey FOREIGN KEY (connection_id) REFERENCES accounting_connections(id) ON DELETE CASCADE; -- 6. 删除旧表 (在确认迁移成功后) -- DROP TABLE fortnox_invoices; -- DROP TABLE fortnox_tenants; -- 7. 创建新索引和视图 -- ... (见相应章节) ``` ### 6.2 初始迁移 (新部署) ```sql -- 启用 UUID 扩展 CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- 创建枚举类型 CREATE TYPE invoice_status AS ENUM ('pending', 'uploading', 'processing', 'preview', 'importing', 'imported', 'failed'); CREATE TYPE supplier_match_action AS ENUM ('USE_EXISTING', 'CREATE_NEW', 'SUGGEST_MATCH'); CREATE TYPE queue_status AS ENUM ('queued', 'processing', 'completed', 'failed', 'cancelled'); -- 创建表 (按依赖顺序) -- 1. users -- 2. accounting_connections -- 3. invoices -- 4. supplier_cache -- 5. invoice_reviews -- 6. processing_queue -- 7. account_mappings -- 8. api_logs -- 9. invoice_status_history -- 创建索引 -- 创建视图 -- 创建函数和触发器 ``` --- ## 7. 性能优化建议 ### 7.1 分区策略 对于 `invoices` 和 `api_logs` 表,建议按时间分区: ```sql -- 按月分区 (示例) CREATE TABLE invoices_partitioned ( LIKE invoices INCLUDING ALL ) PARTITION BY RANGE (created_at); -- 创建分区 CREATE TABLE invoices_2024_01 PARTITION OF invoices_partitioned FOR VALUES FROM ('2024-01-01') TO ('2024-02-01'); CREATE TABLE invoices_2024_02 PARTITION OF invoices_partitioned FOR VALUES FROM ('2024-02-01') TO ('2024-03-01'); ``` ### 7.2 多会计系统查询优化 ```sql -- 为常用查询创建复合索引 CREATE INDEX idx_invoices_provider_status ON invoices(provider, status); CREATE INDEX idx_invoices_connection_created ON invoices(connection_id, created_at DESC); -- 为 provider 统计查询优化 CREATE INDEX idx_connections_user_provider ON accounting_connections(user_id, provider); ``` ### 7.3 清理策略 ```sql -- 定期清理过期缓存 DELETE FROM supplier_cache WHERE expires_at < NOW(); -- 归档旧日志 -- 建议将 90 天前的 api_logs 迁移到冷存储 CREATE TABLE api_logs_archive (LIKE api_logs INCLUDING ALL); -- 归档脚本 INSERT INTO api_logs_archive SELECT * FROM api_logs WHERE created_at < NOW() - INTERVAL '90 days'; DELETE FROM api_logs WHERE created_at < NOW() - INTERVAL '90 days'; ``` --- ## 8. 备份策略 | 数据类型 | 备份频率 | 保留期 | |----------|----------|--------| | 全量备份 | 每日 | 30 天 | | 增量备份 | 每小时 | 7 天 | | 事务日志 | 持续 | 7 天 | ```sql -- 关键表逻辑备份示例 pg_dump --table=accounting_connections --table=invoices --table=users > backup.sql -- 按 provider 备份 pg_dump --table=invoices --where="provider = 'fortnox'" > fortnox_invoices_backup.sql ``` --- ## 9. EF Core 迁移 ### 9.1 创建迁移 ```bash # 添加迁移 cd backend/src/InvoiceMaster.Infrastructure dotnet ef migrations add InitialCreate --startup-project ../InvoiceMaster.API # 更新数据库 dotnet ef database update --startup-project ../InvoiceMaster.API # 生成 SQL 脚本 dotnet ef migrations script --startup-project ../InvoiceMaster.API -o migration.sql ``` ### 9.2 领域事件存储配置 ```csharp // InvoiceMaster.Infrastructure/Data/Configurations/DomainEventConfiguration.cs public class DomainEventConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ToTable("DomainEvents"); builder.HasKey(e => e.Id); builder.Property(e => e.EventType) .IsRequired() .HasMaxLength(255); builder.Property(e => e.AggregateType) .IsRequired() .HasMaxLength(255); builder.Property(e => e.Payload) .IsRequired() .HasColumnType("jsonb"); builder.HasIndex(e => new { e.AggregateType, e.AggregateId }) .HasDatabaseName("IX_DomainEvents_Aggregate"); builder.HasIndex(e => e.OccurredAt) .HasDatabaseName("IX_DomainEvents_OccurredAt"); } } ``` ## 10. Schema 变更日志 ### v3.0 (2026-02-03) **技术栈变更:** - Python/SQLAlchemy → .NET 8/EF Core 8 - 新增 `DomainEvents` 表用于审计 **新增表:** - `DomainEvents` - 领域事件存储(审计) **EF Core 配置:** - 所有表使用 Fluent API 配置 - 启用全局查询过滤器(软删除) - 配置领域事件拦截器 ### v2.0 (2026-02-03) **新增表:** - `accounting_connections` - 替换 `fortnox_tenants`,支持多会计系统 - `invoices` - 替换 `fortnox_invoices`,添加 `provider` 字段 **修改表:** - `supplier_cache`: `tenant_id` → `connection_id` - `account_mappings`: 添加 `connection_id` 和 `provider` 字段 - `api_logs`: `tenant_id` → `connection_id`,`api_name` → `provider` **新增视图:** - `connection_stats` - 替换 `tenant_stats` - `user_connections_view` - 用户连接视图 **新增触发器:** - `set_invoice_provider_trigger` - 自动设置 provider 字段 --- **文档历史:** | 版本 | 日期 | 作者 | 变更 | |------|------|------|------| | 3.0 | 2026-02-03 | Claude Code | 重构为 .NET + EF Core + 审计支持 | | 2.0 | 2026-02-03 | Claude Code | 重构为多会计系统 Schema | | 1.0 | 2026-02-03 | Claude Code | 初始版本 |