# 系统使用统计表设计 ## 1. 设计目标 基于《系统使用统计最终需求》和《系统使用统计接口设计》,设计一套可落地的数据库方案,用于支持以下统计场景: - 登录统计 - 文档上传统计 - 文档评查统计 - 按用户、部门、地区、文档大类、文档类型、入口模块进行动态汇总 - 支持时间范围查询、排行榜、趋势图与明细导出 本次设计遵循两个原则: - 能复用现有业务表的地方尽量复用 - 只为现有表无法满足的统计能力补最小新增表结构 ## 2. 总体设计思路 统计数据来源分为两类: - 现有业务事实表 - 新增统计事件表 其中: - 上传、评查相关统计,尽量直接复用现有业务表 - 登录统计由于当前没有稳定入库明细,需要新增登录事件表 - 为了提升用户管理页的查询效率,建议给 `sso_users` 增加最近登录时间字段 ## 3. 现有可复用表 ### 3.1 用户表 `sso_users` 用途: - 用户基础信息来源 - 部门、地区、组织维度来源 - 最近登录时间展示 当前已使用字段: - `id` - `sub` - `username` - `nick_name` - `phone_number` - `email` - `ou_id` - `ou_name` - `area` - `tenant_name` - `dep_name` - `dep_short_name` - `status` - `deleted_at` 代码参考: - `fastapi_modules/fastapi_leaudit/services/impl/authServiceImpl.py:36` - `fastapi_modules/fastapi_leaudit/services/impl/rbacAdminServiceImpl.py:374` ### 3.2 文档主表 `leaudit_documents` 用途: - 文档主记录 - 文档类型、地区、版本、处理状态来源 主要字段: - `id` - `type_id` - `group_id` - `region` - `processing_status` - `current_run_id` - `version_group_key` - `version_no` - `is_latest_version` - `created_at` - `updated_at` - `deleted_at` 代码参考: - `fastapi_modules/fastapi_leaudit/models/leauditDocument.py:19` ### 3.3 文档文件表 `leaudit_document_files` 用途: - 上传主文件统计 - 上传附件统计 - 上传人、上传时间明细来源 主要字段: - `id` - `document_id` - `file_role` - `file_name` - `file_ext` - `mime_type` - `file_size` - `oss_url` - `is_active` - `created_by` - `created_at` 口径建议: - 主文件上传:`file_role = 'primary'` - 附件上传:`file_role = 'attachment'` 代码参考: - `fastapi_modules/fastapi_leaudit/models/leauditDocumentFile.py:15` - `fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py:224` - `fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py:889` ### 3.4 评查运行表 `leaudit_audit_runs` 用途: - 评查发起次数 - 评查完成次数 - 评查失败次数 - 评查时间趋势 - 评查状态与结果明细 主要字段: - `id` - `document_id` - `document_file_id` - `run_no` - `trigger_source` - `trigger_user_id` - `status` - `phase` - `rule_set_id` - `rule_version_id` - `result_status` - `total_score` - `passed_count` - `failed_count` - `skipped_count` - `started_at` - `finished_at` - `created_at` - `updated_at` 现状说明: - 表结构已预留 `trigger_user_id` - 但当前代码创建 run 时尚未写入该字段 - 统计功能上线前建议补齐该字段写入逻辑 代码参考: - `fastapi_modules/fastapi_leaudit/models/leauditAuditRun.py:16` - `fastapi_modules/fastapi_leaudit/services/impl/auditServiceImpl.py:203` ### 3.5 评查指标表 `leaudit_run_metrics` 用途: - 评查页数 - 评查耗时 - 规则数量等运行指标 主要字段: - `run_id` - `ocr_seconds` - `normalize_seconds` - `extract_seconds` - `evaluate_seconds` - `rescue_seconds` - `total_seconds` - `page_count` - `sub_document_count` - `field_count` - `rule_count` - `llm_call_count` - `vlm_call_count` - `rescue_rule_count` - `artifact_count` 代码参考: - `fastapi_modules/fastapi_leaudit/leaudit_bridge/storage_adapter.py:260` ### 3.6 文档类型表 `leaudit_document_types` 用途: - 文档类型维度统计 - 入口模块归属 建议依赖字段: - `id` - `code` - `name` - `entry_module_id` - `is_enabled` - `sort_order` ### 3.7 入口模块表 `leaudit_entry_modules` 用途: - 入口模块维度统计 - 首页/业务模块使用分析 建议依赖字段: - `id` - `name` - `path` - `sort_order` - `is_enabled` ## 4. 建议调整的现有表 ### 4.1 为 `sso_users` 增加最近登录时间字段 #### 建议新增字段 ```sql ALTER TABLE sso_users ADD COLUMN IF NOT EXISTS last_login_at TIMESTAMPTZ NULL; ``` #### 字段说明 | 字段 | 类型 | 允许空 | 说明 | | --- | --- | --- | --- | | `last_login_at` | `TIMESTAMPTZ` | 是 | 最近一次登录成功时间 | #### 用途 - 用户列表展示“最近登录时间” - 后台快速判断用户活跃情况 - 避免每次都扫登录事件表取最大时间 #### 更新规则 - 登录成功:更新 `last_login_at` - 登录失败:不更新 ## 5. 新增表设计 ### 5.1 登录事件表 `usage_login_events` #### 设计目的 当前系统缺少结构化登录明细表,无法稳定支撑: - 登录次数 - 登录用户数 - 按部门登录统计 - 按地区登录统计 - 登录趋势图 - 最近登录明细导出 因此建议新增登录事件表。 #### 建表建议 ```sql CREATE TABLE IF NOT EXISTS usage_login_events ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NULL, sub VARCHAR(128) NULL, username_snapshot VARCHAR(128) NULL, nick_name_snapshot VARCHAR(128) NULL, department_name_snapshot VARCHAR(255) NULL, ou_id_snapshot VARCHAR(128) NULL, ou_name_snapshot VARCHAR(255) NULL, area_snapshot VARCHAR(64) NULL, login_time TIMESTAMPTZ NOT NULL DEFAULT NOW(), login_result VARCHAR(16) NOT NULL, login_type VARCHAR(32) NOT NULL, ip_address VARCHAR(64) NULL, user_agent VARCHAR(1024) NULL, client_type VARCHAR(32) NULL, token_jti VARCHAR(128) NULL, failure_reason VARCHAR(255) NULL, extra JSONB NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); ``` #### 字段说明 | 字段 | 类型 | 说明 | | --- | --- | --- | | `id` | `BIGSERIAL` | 主键 | | `user_id` | `BIGINT` | 用户 ID,失败登录时可为空 | | `sub` | `VARCHAR(128)` | 登录标识,兼容账号字段 | | `username_snapshot` | `VARCHAR(128)` | 登录时用户名快照 | | `nick_name_snapshot` | `VARCHAR(128)` | 登录时姓名快照 | | `department_name_snapshot` | `VARCHAR(255)` | 登录时部门快照 | | `ou_id_snapshot` | `VARCHAR(128)` | 登录时组织 ID 快照 | | `ou_name_snapshot` | `VARCHAR(255)` | 登录时组织名称快照 | | `area_snapshot` | `VARCHAR(64)` | 登录时地区快照 | | `login_time` | `TIMESTAMPTZ` | 登录时间 | | `login_result` | `VARCHAR(16)` | `success` / `failed` | | `login_type` | `VARCHAR(32)` | `password` / `oauth` | | `ip_address` | `VARCHAR(64)` | 登录来源 IP | | `user_agent` | `VARCHAR(1024)` | 浏览器/终端标识 | | `client_type` | `VARCHAR(32)` | `pc` / `mobile` / `other` | | `token_jti` | `VARCHAR(128)` | 成功登录后的 token jti,可选 | | `failure_reason` | `VARCHAR(255)` | 失败原因 | | `extra` | `JSONB` | 扩展字段 | | `created_at` | `TIMESTAMPTZ` | 记录创建时间 | #### 索引建议 ```sql CREATE INDEX IF NOT EXISTS idx_usage_login_events_login_time ON usage_login_events(login_time DESC); CREATE INDEX IF NOT EXISTS idx_usage_login_events_user_id ON usage_login_events(user_id, login_time DESC); CREATE INDEX IF NOT EXISTS idx_usage_login_events_department ON usage_login_events(department_name_snapshot, login_time DESC); CREATE INDEX IF NOT EXISTS idx_usage_login_events_area ON usage_login_events(area_snapshot, login_time DESC); CREATE INDEX IF NOT EXISTS idx_usage_login_events_result ON usage_login_events(login_result, login_time DESC); ``` #### 统计口径建议 - 登录次数:仅统计 `login_result = 'success'` - 登录失败次数:统计 `login_result = 'failed'` - 最近登录时间:按用户取最后一次成功登录时间 ### 5.2 文档大类映射表 `usage_document_category_mappings`(可选但强烈建议) #### 设计目的 需求中明确要求: - 不写死“合同类”“案卷类” - 后续新增文档大类、文档类型后可自动纳入统计 如果当前系统已经有稳定的一级文档大类实体,可直接复用; 如果没有一张适合统计使用的稳定“大类表”,建议新增一张统计映射表。 #### 建表建议 ```sql CREATE TABLE IF NOT EXISTS usage_document_category_mappings ( id BIGSERIAL PRIMARY KEY, category_code VARCHAR(64) NOT NULL, category_name VARCHAR(128) NOT NULL, document_type_id BIGINT NOT NULL, entry_module_id BIGINT NULL, is_enabled BOOLEAN NOT NULL DEFAULT TRUE, sort_order INTEGER NOT NULL DEFAULT 0, created_by BIGINT NULL, updated_by BIGINT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ NULL ); ``` #### 字段说明 | 字段 | 类型 | 说明 | | --- | --- | --- | | `id` | `BIGSERIAL` | 主键 | | `category_code` | `VARCHAR(64)` | 统计大类编码 | | `category_name` | `VARCHAR(128)` | 统计大类名称 | | `document_type_id` | `BIGINT` | 关联文档类型 ID | | `entry_module_id` | `BIGINT` | 可选,关联入口模块 | | `is_enabled` | `BOOLEAN` | 是否启用 | | `sort_order` | `INTEGER` | 排序 | | `created_by` | `BIGINT` | 创建人 | | `updated_by` | `BIGINT` | 更新人 | | `created_at` | `TIMESTAMPTZ` | 创建时间 | | `updated_at` | `TIMESTAMPTZ` | 更新时间 | | `deleted_at` | `TIMESTAMPTZ` | 软删除时间 | #### 约束与索引建议 ```sql CREATE UNIQUE INDEX IF NOT EXISTS uq_usage_document_category_type ON usage_document_category_mappings(document_type_id) WHERE deleted_at IS NULL; CREATE INDEX IF NOT EXISTS idx_usage_document_category_code ON usage_document_category_mappings(category_code, is_enabled); CREATE INDEX IF NOT EXISTS idx_usage_document_category_module ON usage_document_category_mappings(entry_module_id, is_enabled); ``` #### 说明 - 一种文档类型只归属一个统计大类 - 若后续系统已有成熟的一级文档大类表,可取消本表,直接复用业务表 ## 6. 查询口径与表关系建议 ### 6.1 登录统计 来源: - `usage_login_events` - `sso_users.last_login_at` 用途: - 登录次数 - 登录用户数 - 最近登录时间 - 按部门、地区统计登录情况 ### 6.2 上传统计 来源: - `leaudit_document_files` - `leaudit_documents` - `sso_users` - `leaudit_document_types` - `leaudit_entry_modules` - `usage_document_category_mappings`(若启用) 口径: - 主文件上传:`leaudit_document_files.file_role = 'primary'` - 附件上传:`leaudit_document_files.file_role = 'attachment'` ### 6.3 评查统计 来源: - `leaudit_audit_runs` - `leaudit_documents` - `leaudit_document_types` - `leaudit_entry_modules` - `usage_document_category_mappings`(若启用) 口径: - 发起评查次数:`leaudit_audit_runs` 记录数 - 完成评查次数:`status = 'completed'` - 失败评查次数:`status = 'failed'` ### 6.4 地区统计 支持两种口径: - 用户地区口径 - 登录:取 `usage_login_events.area_snapshot` - 上传:取上传人 `sso_users.area` - 评查:优先取触发人地区 - 文档地区口径 - 上传:取 `leaudit_documents.region` - 评查:取 `leaudit_documents.region` ## 7. 一期是否需要聚合表 ### 7.1 一期建议 一期先不新增日汇总聚合表,先使用: - 明细事实表 + SQL 聚合 原因: - 功能验证阶段,需求还可能变 - 统计范围目前只有登录、上传、评查三类,复杂度可控 - 先把口径跑通,比过早做聚合更重要 ### 7.2 二期建议 若后续数据量变大,再补以下日汇总表: - `usage_user_daily_stats` - `usage_department_daily_stats` - `usage_area_daily_stats` - `usage_document_category_daily_stats` - `usage_entry_module_daily_stats` ## 8. 配套代码改造建议 ### 8.1 登录成功时 需要做两件事: - 写入 `usage_login_events` - 更新 `sso_users.last_login_at` ### 8.2 登录失败时 建议: - 写入 `usage_login_events` - 不更新 `sso_users.last_login_at` ### 8.3 触发评查时 建议补齐: - `leaudit_audit_runs.trigger_user_id` 当前表已存在该字段,但创建 run 时未写入,需要补逻辑。 ## 9. 推荐 SQL 变更清单 ### 9.1 为 `sso_users` 增加最近登录时间 ```sql ALTER TABLE sso_users ADD COLUMN IF NOT EXISTS last_login_at TIMESTAMPTZ NULL; ``` ### 9.2 新增登录事件表 ```sql CREATE TABLE IF NOT EXISTS usage_login_events ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NULL, sub VARCHAR(128) NULL, username_snapshot VARCHAR(128) NULL, nick_name_snapshot VARCHAR(128) NULL, department_name_snapshot VARCHAR(255) NULL, ou_id_snapshot VARCHAR(128) NULL, ou_name_snapshot VARCHAR(255) NULL, area_snapshot VARCHAR(64) NULL, login_time TIMESTAMPTZ NOT NULL DEFAULT NOW(), login_result VARCHAR(16) NOT NULL, login_type VARCHAR(32) NOT NULL, ip_address VARCHAR(64) NULL, user_agent VARCHAR(1024) NULL, client_type VARCHAR(32) NULL, token_jti VARCHAR(128) NULL, failure_reason VARCHAR(255) NULL, extra JSONB NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); ``` ### 9.3 新增登录事件索引 ```sql CREATE INDEX IF NOT EXISTS idx_usage_login_events_login_time ON usage_login_events(login_time DESC); CREATE INDEX IF NOT EXISTS idx_usage_login_events_user_id ON usage_login_events(user_id, login_time DESC); CREATE INDEX IF NOT EXISTS idx_usage_login_events_department ON usage_login_events(department_name_snapshot, login_time DESC); CREATE INDEX IF NOT EXISTS idx_usage_login_events_area ON usage_login_events(area_snapshot, login_time DESC); CREATE INDEX IF NOT EXISTS idx_usage_login_events_result ON usage_login_events(login_result, login_time DESC); ``` ### 9.4 新增统计大类映射表(可选) ```sql CREATE TABLE IF NOT EXISTS usage_document_category_mappings ( id BIGSERIAL PRIMARY KEY, category_code VARCHAR(64) NOT NULL, category_name VARCHAR(128) NOT NULL, document_type_id BIGINT NOT NULL, entry_module_id BIGINT NULL, is_enabled BOOLEAN NOT NULL DEFAULT TRUE, sort_order INTEGER NOT NULL DEFAULT 0, created_by BIGINT NULL, updated_by BIGINT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), deleted_at TIMESTAMPTZ NULL ); ``` ## 10. 一期最终建议 一期最小可落地表设计建议如下: 必须做: - `sso_users.last_login_at` - `usage_login_events` - 补齐 `leaudit_audit_runs.trigger_user_id` 写入逻辑 建议做: - `usage_document_category_mappings` 可后置: - 各类日汇总聚合表 - 复杂会话表 - 全量行为审计表