From d5c06eca2886b3284857a9ab5400aa949b592c19 Mon Sep 17 00:00:00 2001 From: wangys <3401275564@qq.com> Date: Thu, 16 Apr 2026 15:46:29 +0800 Subject: [PATCH] =?UTF-8?q?sql=E6=9B=B4=E6=96=B0=E3=80=81=E6=9E=B6?= =?UTF-8?q?=E6=9E=84=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .devcontainer/external/devcontainer.json | 2 +- .devcontainer/external/docker-compose.yml | 7 + .devcontainer/internal/devcontainer.json | 15 +- .devcontainer/internal/docker-compose.yml | 116 + .../docker-compose.public-services.yml | 96 + docker/dev-common/docker-compose.yml | 109 + docs/1/postgres - k12study - achievement.svg | 74980 --------- docs/1/postgres - k12study - ai.graphml | 178 - docs/1/postgres - k12study - auth.svg | 12945 -- docs/1/postgres - k12study - course.svg | 90910 ---------- docs/1/postgres - k12study - question.svg | 128021 --------------- .../postgres - k12study - recommendation.svg | 79021 --------- docs/architecture.md | 135 +- docs/architecture/README.md | 30 + docs/architecture/api-design.md | 82 + docs/architecture/base-services.md | 59 + docs/architecture/er-diagram.md | 77 + docs/architecture/logical-view.md | 77 + .../modules/01-课程学习/er图.drawio | 20 +- .../modules/01-课程学习/数据流图.drawio | 2 +- .../modules/02-习题与作业/er图.drawio | 16 +- .../modules/03-批改与反馈/er图.drawio | 18 +- .../modules/03-批改与反馈/数据流图.drawio | 16 +- docs/architecture/代码架构.drawio | 136 + docs/architecture/完整业务流程图.drawio | 97 +- docs/architecture/数据流图(多角色).drawio | 330 +- docs/architecture/系统架构.drawio | 205 + docs/architecture/系统架构与代码架构.drawio | 450 - docs/milestone.md | 81 + docs/plan/architecture.md | 205 - ...学习-习题-批改ER与对象类数据流图实施计划.md | 44 - init/pg/01_create_schema.sql | 1 - init/pg/02_init_modules.sql | 1 - .../pg/question/10_create_question_tables.sql | 545 +- init/pg/question/20_init_question_seed.sql | 47 + .../10_create_recommendation_tables.sql | 440 - 36 files changed, 2099 insertions(+), 387415 deletions(-) delete mode 100644 docs/1/postgres - k12study - achievement.svg delete mode 100644 docs/1/postgres - k12study - ai.graphml delete mode 100644 docs/1/postgres - k12study - auth.svg delete mode 100644 docs/1/postgres - k12study - course.svg delete mode 100644 docs/1/postgres - k12study - question.svg delete mode 100644 docs/1/postgres - k12study - recommendation.svg create mode 100644 docs/architecture/README.md create mode 100644 docs/architecture/api-design.md create mode 100644 docs/architecture/base-services.md create mode 100644 docs/architecture/er-diagram.md create mode 100644 docs/architecture/logical-view.md create mode 100644 docs/architecture/代码架构.drawio create mode 100644 docs/architecture/系统架构.drawio delete mode 100644 docs/architecture/系统架构与代码架构.drawio create mode 100644 docs/milestone.md delete mode 100644 docs/plan/architecture.md delete mode 100644 docs/plan/课程学习-习题-批改ER与对象类数据流图实施计划.md delete mode 100644 init/pg/recommendation/10_create_recommendation_tables.sql diff --git a/.devcontainer/external/devcontainer.json b/.devcontainer/external/devcontainer.json index 451c49a..b57984e 100644 --- a/.devcontainer/external/devcontainer.json +++ b/.devcontainer/external/devcontainer.json @@ -6,7 +6,7 @@ "service": "workspace", "workspaceFolder": "/workspaces/K12study", "shutdownAction": "stopCompose", - "forwardPorts": [5173, 8080, 8081, 8082, 8088, 9000], + "forwardPorts": [5173, 8080, 8081, 8082, 8088, 9000, 8848, 9848, 9849, 9559, 9779, 9669, 19530, 9091], "customizations": { "vscode": { "settings": { diff --git a/.devcontainer/external/docker-compose.yml b/.devcontainer/external/docker-compose.yml index 5dfd407..305bba1 100644 --- a/.devcontainer/external/docker-compose.yml +++ b/.devcontainer/external/docker-compose.yml @@ -18,6 +18,13 @@ services: K12STUDY_REGISTRY_PORT: ${K12STUDY_REGISTRY_PORT:-8848} K12STUDY_REGISTRY_GRPC_PORT: ${K12STUDY_REGISTRY_GRPC_PORT:-9848} K12STUDY_REGISTRY_RAFT_PORT: ${K12STUDY_REGISTRY_RAFT_PORT:-9849} + K12STUDY_GRAPH_HOST: ${K12STUDY_GRAPH_HOST:-host.docker.internal} + K12STUDY_GRAPH_PORT: ${K12STUDY_GRAPH_PORT:-9669} + K12STUDY_GRAPH_USER: ${K12STUDY_GRAPH_USER:-root} + K12STUDY_GRAPH_PASSWORD: ${K12STUDY_GRAPH_PASSWORD:-nebula} + K12STUDY_VECTOR_HOST: ${K12STUDY_VECTOR_HOST:-host.docker.internal} + K12STUDY_VECTOR_PORT: ${K12STUDY_VECTOR_PORT:-19530} + K12STUDY_VECTOR_HTTP_PORT: ${K12STUDY_VECTOR_HTTP_PORT:-9091} PYTHONUNBUFFERED: "1" extra_hosts: - "host.docker.internal:host-gateway" diff --git a/.devcontainer/internal/devcontainer.json b/.devcontainer/internal/devcontainer.json index 79c4989..61dad36 100644 --- a/.devcontainer/internal/devcontainer.json +++ b/.devcontainer/internal/devcontainer.json @@ -5,9 +5,20 @@ ], "service": "workspace", "workspaceFolder": "/workspaces/K12study", - "runServices": ["workspace", "postgres", "redis"], + "runServices": [ + "workspace", + "postgres", + "redis", + "nacos", + "nebula-metad", + "nebula-storaged", + "nebula-graphd", + "milvus-etcd", + "milvus-minio", + "milvus" + ], "shutdownAction": "stopCompose", - "forwardPorts": [5173, 8080, 8081, 8082, 8088, 9000, 5432, 6379], + "forwardPorts": [5173, 8080, 8081, 8082, 8088, 9000, 5432, 6379, 8848, 9848, 9849, 9559, 9779, 9669, 19530, 9091], "customizations": { "vscode": { "settings": { diff --git a/.devcontainer/internal/docker-compose.yml b/.devcontainer/internal/docker-compose.yml index 15f172a..8de14a7 100644 --- a/.devcontainer/internal/docker-compose.yml +++ b/.devcontainer/internal/docker-compose.yml @@ -14,10 +14,24 @@ services: K12STUDY_REDIS_HOST: redis K12STUDY_REDIS_PORT: 6379 K12STUDY_REDIS_PASSWORD: "" + K12STUDY_REGISTRY_HOST: nacos + K12STUDY_REGISTRY_PORT: 8848 + K12STUDY_REGISTRY_GRPC_PORT: 9848 + K12STUDY_REGISTRY_RAFT_PORT: 9849 + K12STUDY_GRAPH_HOST: nebula-graphd + K12STUDY_GRAPH_PORT: 9669 + K12STUDY_GRAPH_USER: root + K12STUDY_GRAPH_PASSWORD: nebula + K12STUDY_VECTOR_HOST: milvus + K12STUDY_VECTOR_PORT: 19530 + K12STUDY_VECTOR_HTTP_PORT: 9091 PYTHONUNBUFFERED: "1" depends_on: - postgres - redis + - nacos + - nebula-graphd + - milvus postgres: image: postgres:16 @@ -40,7 +54,109 @@ services: command: redis-server --appendonly yes volumes: - redis-dev-data:/data + nacos: + image: nacos/nacos-server:v3.2.1-2026.04.03 + restart: unless-stopped + environment: + MODE: standalone + NACOS_AUTH_ENABLE: "false" + TZ: Asia/Shanghai + JVM_XMS: 256m + JVM_XMX: 512m + JVM_XMN: 128m + ports: + - "8848:8848" + - "9848:9848" + - "9849:9849" + volumes: + - nacos-dev-logs:/home/nacos/logs + - nacos-dev-data:/home/nacos/data + nebula-metad: + image: vesoft/nebula-metad:v3.8.0 + restart: unless-stopped + command: + - --meta_server_addrs=nebula-metad:9559 + - --local_ip=nebula-metad + - --ws_ip=0.0.0.0 + - --port=9559 + - --data_path=/data/meta + ports: + - "9559:9559" + volumes: + - nebula-meta-dev-data:/data/meta + nebula-storaged: + image: vesoft/nebula-storaged:v3.8.0 + restart: unless-stopped + command: + - --meta_server_addrs=nebula-metad:9559 + - --local_ip=nebula-storaged + - --ws_ip=0.0.0.0 + - --port=9779 + - --data_path=/data/storage + depends_on: + - nebula-metad + ports: + - "9779:9779" + volumes: + - nebula-storage-dev-data:/data/storage + nebula-graphd: + image: vesoft/nebula-graphd:v3.8.0 + restart: unless-stopped + command: + - --meta_server_addrs=nebula-metad:9559 + - --local_ip=nebula-graphd + - --ws_ip=0.0.0.0 + - --port=9669 + depends_on: + - nebula-metad + - nebula-storaged + ports: + - "9669:9669" + milvus-etcd: + image: quay.io/coreos/etcd:v3.5.5 + restart: unless-stopped + environment: + ETCD_AUTO_COMPACTION_MODE: revision + ETCD_AUTO_COMPACTION_RETENTION: "1000" + ETCD_QUOTA_BACKEND_BYTES: "4294967296" + ETCD_SNAPSHOT_COUNT: "50000" + command: etcd -advertise-client-urls=http://milvus-etcd:2379 -listen-client-urls=http://0.0.0.0:2379 --data-dir=/etcd + volumes: + - milvus-etcd-dev-data:/etcd + milvus-minio: + image: minio/minio:RELEASE.2024-11-07T00-52-20Z + restart: unless-stopped + environment: + MINIO_ACCESS_KEY: minioadmin + MINIO_SECRET_KEY: minioadmin + command: minio server /data + ports: + - "9000:9000" + volumes: + - milvus-minio-dev-data:/data + milvus: + image: milvusdb/milvus:v2.4.8 + restart: unless-stopped + command: ["milvus", "run", "standalone"] + depends_on: + - milvus-etcd + - milvus-minio + environment: + ETCD_ENDPOINTS: milvus-etcd:2379 + MINIO_ADDRESS: milvus-minio:9000 + ports: + - "19530:19530" + - "9091:9091" + volumes: + - milvus-dev-data:/var/lib/milvus volumes: postgres-dev-data: redis-dev-data: + nacos-dev-logs: + nacos-dev-data: + nebula-meta-dev-data: + nebula-storage-dev-data: + milvus-etcd-dev-data: + milvus-minio-dev-data: + milvus-dev-data: diff --git a/docker/dev-common/docker-compose.public-services.yml b/docker/dev-common/docker-compose.public-services.yml index b096290..d6890cb 100644 --- a/docker/dev-common/docker-compose.public-services.yml +++ b/docker/dev-common/docker-compose.public-services.yml @@ -61,6 +61,102 @@ services: - ${K12STUDY_RUN_DIR:-./runtime}/nacos/data:/home/nacos/data networks: - k12study-dev-net + nebula-metad: + image: vesoft/nebula-metad:v3.8.0 + container_name: k12study-dev-nebula-metad + restart: unless-stopped + command: + - --meta_server_addrs=nebula-metad:9559 + - --local_ip=nebula-metad + - --ws_ip=0.0.0.0 + - --port=9559 + - --data_path=/data/meta + ports: + - "${K12STUDY_NEBULA_META_PORT:-9559}:9559" + volumes: + - ${K12STUDY_RUN_DIR:-./runtime}/nebula/meta:/data/meta + networks: + - k12study-dev-net + nebula-storaged: + image: vesoft/nebula-storaged:v3.8.0 + container_name: k12study-dev-nebula-storaged + restart: unless-stopped + command: + - --meta_server_addrs=nebula-metad:9559 + - --local_ip=nebula-storaged + - --ws_ip=0.0.0.0 + - --port=9779 + - --data_path=/data/storage + depends_on: + - nebula-metad + ports: + - "${K12STUDY_NEBULA_STORAGE_PORT:-9779}:9779" + volumes: + - ${K12STUDY_RUN_DIR:-./runtime}/nebula/storage:/data/storage + networks: + - k12study-dev-net + nebula-graphd: + image: vesoft/nebula-graphd:v3.8.0 + container_name: k12study-dev-nebula-graphd + restart: unless-stopped + command: + - --meta_server_addrs=nebula-metad:9559 + - --local_ip=nebula-graphd + - --ws_ip=0.0.0.0 + - --port=9669 + depends_on: + - nebula-metad + - nebula-storaged + ports: + - "${K12STUDY_GRAPH_PORT:-9669}:9669" + networks: + - k12study-dev-net + milvus-etcd: + image: quay.io/coreos/etcd:v3.5.5 + container_name: k12study-dev-milvus-etcd + restart: unless-stopped + environment: + ETCD_AUTO_COMPACTION_MODE: revision + ETCD_AUTO_COMPACTION_RETENTION: "1000" + ETCD_QUOTA_BACKEND_BYTES: "4294967296" + ETCD_SNAPSHOT_COUNT: "50000" + command: etcd -advertise-client-urls=http://milvus-etcd:2379 -listen-client-urls=http://0.0.0.0:2379 --data-dir=/etcd + volumes: + - ${K12STUDY_RUN_DIR:-./runtime}/milvus/etcd:/etcd + networks: + - k12study-dev-net + milvus-minio: + image: minio/minio:RELEASE.2024-11-07T00-52-20Z + container_name: k12study-dev-milvus-minio + restart: unless-stopped + environment: + MINIO_ACCESS_KEY: ${K12STUDY_MINIO_ACCESS_KEY:-minioadmin} + MINIO_SECRET_KEY: ${K12STUDY_MINIO_SECRET_KEY:-minioadmin} + command: minio server /data + ports: + - "${K12STUDY_MINIO_PORT:-9000}:9000" + volumes: + - ${K12STUDY_RUN_DIR:-./runtime}/milvus/minio:/data + networks: + - k12study-dev-net + milvus: + image: milvusdb/milvus:v2.4.8 + container_name: k12study-dev-milvus + restart: unless-stopped + command: ["milvus", "run", "standalone"] + depends_on: + - milvus-etcd + - milvus-minio + environment: + ETCD_ENDPOINTS: milvus-etcd:2379 + MINIO_ADDRESS: milvus-minio:9000 + ports: + - "${K12STUDY_VECTOR_PORT:-19530}:19530" + - "${K12STUDY_VECTOR_HTTP_PORT:-9091}:9091" + volumes: + - ${K12STUDY_RUN_DIR:-./runtime}/milvus/data:/var/lib/milvus + networks: + - k12study-dev-net networks: k12study-dev-net: diff --git a/docker/dev-common/docker-compose.yml b/docker/dev-common/docker-compose.yml index ba83eb2..0b71263 100644 --- a/docker/dev-common/docker-compose.yml +++ b/docker/dev-common/docker-compose.yml @@ -23,7 +23,116 @@ services: command: redis-server --appendonly yes volumes: - redis-data:/data + nacos: + image: nacos/nacos-server:v3.2.1-2026.04.03 + container_name: k12study-nacos + restart: unless-stopped + environment: + MODE: standalone + NACOS_AUTH_ENABLE: "false" + TZ: Asia/Shanghai + JVM_XMS: 256m + JVM_XMX: 512m + JVM_XMN: 128m + ports: + - "8848:8848" + - "9848:9848" + - "9849:9849" + volumes: + - nacos-logs:/home/nacos/logs + - nacos-data:/home/nacos/data + nebula-metad: + image: vesoft/nebula-metad:v3.8.0 + container_name: k12study-nebula-metad + restart: unless-stopped + command: + - --meta_server_addrs=nebula-metad:9559 + - --local_ip=nebula-metad + - --ws_ip=0.0.0.0 + - --port=9559 + - --data_path=/data/meta + ports: + - "9559:9559" + volumes: + - nebula-meta-data:/data/meta + nebula-storaged: + image: vesoft/nebula-storaged:v3.8.0 + container_name: k12study-nebula-storaged + restart: unless-stopped + command: + - --meta_server_addrs=nebula-metad:9559 + - --local_ip=nebula-storaged + - --ws_ip=0.0.0.0 + - --port=9779 + - --data_path=/data/storage + depends_on: + - nebula-metad + ports: + - "9779:9779" + volumes: + - nebula-storage-data:/data/storage + nebula-graphd: + image: vesoft/nebula-graphd:v3.8.0 + container_name: k12study-nebula-graphd + restart: unless-stopped + command: + - --meta_server_addrs=nebula-metad:9559 + - --local_ip=nebula-graphd + - --ws_ip=0.0.0.0 + - --port=9669 + depends_on: + - nebula-metad + - nebula-storaged + ports: + - "9669:9669" + milvus-etcd: + image: quay.io/coreos/etcd:v3.5.5 + container_name: k12study-milvus-etcd + restart: unless-stopped + environment: + ETCD_AUTO_COMPACTION_MODE: revision + ETCD_AUTO_COMPACTION_RETENTION: "1000" + ETCD_QUOTA_BACKEND_BYTES: "4294967296" + ETCD_SNAPSHOT_COUNT: "50000" + command: etcd -advertise-client-urls=http://milvus-etcd:2379 -listen-client-urls=http://0.0.0.0:2379 --data-dir=/etcd + volumes: + - milvus-etcd-data:/etcd + milvus-minio: + image: minio/minio:RELEASE.2024-11-07T00-52-20Z + container_name: k12study-milvus-minio + restart: unless-stopped + environment: + MINIO_ACCESS_KEY: minioadmin + MINIO_SECRET_KEY: minioadmin + command: minio server /data + ports: + - "9000:9000" + volumes: + - milvus-minio-data:/data + milvus: + image: milvusdb/milvus:v2.4.8 + container_name: k12study-milvus + restart: unless-stopped + command: ["milvus", "run", "standalone"] + depends_on: + - milvus-etcd + - milvus-minio + environment: + ETCD_ENDPOINTS: milvus-etcd:2379 + MINIO_ADDRESS: milvus-minio:9000 + ports: + - "19530:19530" + - "9091:9091" + volumes: + - milvus-data:/var/lib/milvus volumes: postgres-data: redis-data: + nacos-logs: + nacos-data: + nebula-meta-data: + nebula-storage-data: + milvus-etcd-data: + milvus-minio-data: + milvus-data: diff --git a/docs/1/postgres - k12study - achievement.svg b/docs/1/postgres - k12study - achievement.svg deleted file mode 100644 index 7c9b859..0000000 --- a/docs/1/postgres - k12study - achievement.svg +++ /dev/null @@ -1,74980 +0,0 @@ - - -ac_achievement?????textac_achievement_event_dict?????????????????textac_achievement_metric_def???????????????ac_achievement_role_rel??-?????ac_achievement_rule_template???????????????ac_user_achievement???????int4ac_user_achievement_daily_fact?????????????????????ac_user_achievement_progress???????????????ac_achievement?????textac_achievement_event_dict?????????????????textac_achievement_metric_def???????????????ac_achievement_role_rel??-?????ac_achievement_rule_template???????????????ac_user_achievement???????int4ac_user_achievement_daily_fact?????????????????????ac_user_achievement_progress???????????????ac_achievement?????textac_achievement_event_dict?????????????????textac_achievement_metric_def???????????????ac_achievement_role_rel??-?????ac_achievement_rule_template???????????????ac_user_achievement???????int4ac_user_achievement_daily_fact?????????????????????ac_user_achievement_progress???????????????ac_achievement?????textac_achievement_event_dict?????????????????textac_achievement_metric_def???????????????ac_achievement_role_rel??-?????ac_achievement_rule_template???????????????ac_user_achievement???????int4ac_user_achievement_daily_fact?????????????????????ac_user_achievement_progress???????????????ac_achievement?????textac_achievement_event_dict?????????????????textac_achievement_metric_def???????????????ac_achievement_role_rel??-?????ac_achievement_rule_template???????????????ac_user_achievement???????int4ac_user_achievement_daily_fact?????????????????????ac_user_achievement_progress???????????????1..n11..n11..n11..n11..n11..n11..n11..n1ac_achievement?????textac_achievement_event_dict?????????????????textac_achievement_metric_def???????????????ac_achievement_role_rel??-?????ac_achievement_rule_template???????????????ac_user_achievement???????int4ac_user_achievement_daily_fact?????????????????????ac_user_achievement_progress???????????????ac_achievement?????textac_achievement_event_dict?????????????????textac_achievement_metric_def???????????????ac_achievement_role_rel??-?????ac_achievement_rule_template???????????????ac_user_achievement???????int4ac_user_achievement_daily_fact?????????????????????ac_user_achievement_progress???????????????ac_achievement?????textac_achievement_event_dict?????????????????textac_achievement_metric_def???????????????ac_achievement_role_rel??-?????ac_achievement_rule_template???????????????ac_user_achievement???????int4ac_user_achievement_daily_fact?????????????????????ac_user_achievement_progress???????????????ac_achievement?????textac_achievement_event_dict?????????????????textac_achievement_metric_def???????????????ac_achievement_role_rel??-?????ac_achievement_rule_template???????????????ac_user_achievement???????int4ac_user_achievement_daily_fact?????????????????????ac_user_achievement_progress???????????????ac_achievement?????textac_achievement_event_dict?????????????????textac_achievement_metric_def???????????????ac_achievement_role_rel??-?????ac_achievement_rule_template???????????????ac_user_achievement???????int4ac_user_achievement_daily_fact?????????????????????ac_user_achievement_progress???????????????ac_achievement?????textac_achievement_event_dict?????????????????textac_achievement_metric_def???????????????ac_achievement_role_rel??-?????ac_achievement_rule_template???????????????ac_user_achievement???????int4ac_user_achievement_daily_fact?????????????????????ac_user_achievement_progress???????????????1..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n1 diff --git a/docs/1/postgres - k12study - ai.graphml b/docs/1/postgres - k12study - ai.graphml deleted file mode 100644 index 8380499..0000000 --- a/docs/1/postgres - k12study - ai.graphml +++ /dev/null @@ -1,178 +0,0 @@ - - - - - - - - - - - - tb_ai_graph_entity - entity_id: varchar(64) NOT NULL - 实体ID -file_id: varchar(64) - 来源知识文件ID -entity_type: varchar(64) NOT NULL - 实体类型 -entity_name: varchar(255) NOT NULL - 实体名称 -normalized_name: varchar(255) - 标准化实体名称 -graph_vertex_id: varchar(128) - 图数据库顶点ID -entity_status: varchar(32) NOT NULL - 实体状态 -sync_status: varchar(32) NOT NULL - 同步状态 -properties_json: jsonb NOT NULL - 实体属性JSON -adcode: varchar(12) - 行政区划编码 -tenant_id: varchar(64) - 租户ID -tenant_path: varchar(255) - 租户路径 -dept_id: varchar(64) - 部门ID -dept_path: varchar(255) - 部门路径 -created_at: timestamp NOT NULL - 创建时间 -updated_at: timestamp NOT NULL - 更新时间 - - - - - - - - - - - - - - - - tb_ai_graph_relation - relation_id: varchar(64) NOT NULL - 关系ID -src_entity_id: varchar(64) NOT NULL - 源实体ID -dst_entity_id: varchar(64) NOT NULL - 目标实体ID -relation_type: varchar(64) NOT NULL - 关系类型 -relation_name: varchar(128) - 关系名称 -graph_edge_id: varchar(128) - 图数据库边ID -relation_weight: numeric(6, 4) NOT NULL - 关系权重 -relation_status: varchar(32) NOT NULL - 关系状态 -sync_status: varchar(32) NOT NULL - 同步状态 -properties_json: jsonb NOT NULL - 关系属性JSON -adcode: varchar(12) - 行政区划编码 -tenant_id: varchar(64) - 租户ID -tenant_path: varchar(255) - 租户路径 -dept_id: varchar(64) - 部门ID -dept_path: varchar(255) - 部门路径 -created_at: timestamp NOT NULL - 创建时间 -updated_at: timestamp NOT NULL - 更新时间 - - - - - - - - - - - - - - - - tb_ai_knowledge_file - file_id: varchar(64) NOT NULL - 知识文件ID -file_code: varchar(64) NOT NULL - 知识文件编码 -file_name: varchar(255) NOT NULL - 知识文件名称 -source_file_id: varchar(64) - 来源文件ID(upms.tb_sys_file.file_id) -file_type: varchar(32) NOT NULL - 文件类型 -file_status: varchar(32) NOT NULL - 文件状态 -graph_sync_status: varchar(32) NOT NULL - 图谱同步状态 -vector_sync_status: varchar(32) NOT NULL - 向量库同步状态 -graph_doc_ref: varchar(128) - 图谱侧文档/对象引用 -vector_doc_ref: varchar(128) - 向量库侧文档/对象引用 -content_checksum: varchar(128) - 内容校验值 -metadata_json: jsonb NOT NULL - 文件元数据JSON -adcode: varchar(12) - 行政区划编码 -tenant_id: varchar(64) - 租户ID -tenant_path: varchar(255) - 租户路径 -dept_id: varchar(64) - 部门ID -dept_path: varchar(255) - 部门路径 -created_at: timestamp NOT NULL - 创建时间 -updated_at: timestamp NOT NULL - 更新时间 - - - - - - - - - - - - - - - - tb_ai_knowledge_sync_task - task_id: varchar(64) NOT NULL - 同步任务ID -file_id: varchar(64) NOT NULL - 知识文件ID -target_store: varchar(16) NOT NULL - 目标存储(GRAPH/VECTOR) -task_type: varchar(16) NOT NULL - 任务类型(UPSERT/DELETE/REBUILD) -task_status: varchar(32) NOT NULL - 任务状态 -retry_count: int4 NOT NULL - 重试次数 -error_message: text - 错误信息 -payload_json: jsonb NOT NULL - 任务载荷JSON -adcode: varchar(12) - 行政区划编码 -tenant_id: varchar(64) - 租户ID -tenant_path: varchar(255) - 租户路径 -dept_id: varchar(64) - 部门ID -dept_path: varchar(255) - 部门路径 -created_at: timestamp NOT NULL - 创建时间 -updated_at: timestamp NOT NULL - 更新时间 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/1/postgres - k12study - auth.svg b/docs/1/postgres - k12study - auth.svg deleted file mode 100644 index a8f3cf8..0000000 --- a/docs/1/postgres - k12study - auth.svg +++ /dev/null @@ -1,12945 +0,0 @@ - - -tb_auth_login_audit???????tb_auth_refresh_token???????tb_auth_login_audit???????tb_auth_refresh_token???????tb_auth_login_audit???????tb_auth_refresh_token???????tb_auth_login_audit???????tb_auth_refresh_token???????tb_auth_login_audit???????tb_auth_refresh_token???????tb_auth_login_audit???????tb_auth_refresh_token???????tb_auth_login_audit???????tb_auth_refresh_token???????tb_auth_login_audit???????tb_auth_refresh_token???????tb_auth_login_audit???????tb_auth_refresh_token???????tb_auth_login_audit???????tb_auth_refresh_token???????tb_auth_login_audit???????tb_auth_refresh_token??????? diff --git a/docs/1/postgres - k12study - course.svg b/docs/1/postgres - k12study - course.svg deleted file mode 100644 index 4c54667..0000000 --- a/docs/1/postgres - k12study - course.svg +++ /dev/null @@ -1,90910 +0,0 @@ - - -cl_course????cl_course_chapter?????cl_course_knowledge_rel??-??????cl_course_lesson???int4cl_course_resource?????cl_course_tag?????cl_course_tag_rel??-?????cl_knowledge_point????textcl_kp_material_rel???-???????cl_kp_prerequisite_rel????????cl_learning_event???????cl_learning_progress?????cl_learning_session?????cl_student_kp_mastery?????????cl_course????cl_course_chapter?????cl_course_knowledge_rel??-??????cl_course_lesson???int4cl_course_resource?????cl_course_tag?????cl_course_tag_rel??-?????cl_knowledge_point????textcl_kp_material_rel???-???????cl_kp_prerequisite_rel????????cl_learning_event???????cl_learning_progress?????cl_learning_session?????cl_student_kp_mastery?????????cl_course????cl_course_chapter?????cl_course_knowledge_rel??-??????cl_course_lesson???int4cl_course_resource?????cl_course_tag?????cl_course_tag_rel??-?????cl_knowledge_point????textcl_kp_material_rel???-???????cl_kp_prerequisite_rel????????cl_learning_event???????cl_learning_progress?????cl_learning_session?????cl_student_kp_mastery?????????cl_course????cl_course_chapter?????cl_course_knowledge_rel??-??????cl_course_lesson???int4cl_course_resource?????cl_course_tag?????cl_course_tag_rel??-?????cl_knowledge_point????textcl_kp_material_rel???-???????cl_kp_prerequisite_rel????????cl_learning_event???????cl_learning_progress?????cl_learning_session?????cl_student_kp_mastery?????????cl_course????cl_course_chapter?????cl_course_knowledge_rel??-??????cl_course_lesson???int4cl_course_resource?????cl_course_tag?????cl_course_tag_rel??-?????cl_knowledge_point????textcl_kp_material_rel???-???????cl_kp_prerequisite_rel????????cl_learning_event???????cl_learning_progress?????cl_learning_session?????cl_student_kp_mastery?????????1..n11..n11..n11..n11..n11..n0..11..n11..n11..n11..n11..n0..11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n0..11..n11..n11..n11..n11..n0..11..n11..n11..n11..n11..n11..n1cl_course????cl_course_chapter?????cl_course_knowledge_rel??-??????cl_course_lesson???int4cl_course_resource?????cl_course_tag?????cl_course_tag_rel??-?????cl_knowledge_point????textcl_kp_material_rel???-???????cl_kp_prerequisite_rel????????cl_learning_event???????cl_learning_progress?????cl_learning_session?????cl_student_kp_mastery?????????cl_course????cl_course_chapter?????cl_course_knowledge_rel??-??????cl_course_lesson???int4cl_course_resource?????cl_course_tag?????cl_course_tag_rel??-?????cl_knowledge_point????textcl_kp_material_rel???-???????cl_kp_prerequisite_rel????????cl_learning_event???????cl_learning_progress?????cl_learning_session?????cl_student_kp_mastery?????????cl_course????cl_course_chapter?????cl_course_knowledge_rel??-??????cl_course_lesson???int4cl_course_resource?????cl_course_tag?????cl_course_tag_rel??-?????cl_knowledge_point????textcl_kp_material_rel???-???????cl_kp_prerequisite_rel????????cl_learning_event???????cl_learning_progress?????cl_learning_session?????cl_student_kp_mastery?????????cl_course????cl_course_chapter?????cl_course_knowledge_rel??-??????cl_course_lesson???int4cl_course_resource?????cl_course_tag?????cl_course_tag_rel??-?????cl_knowledge_point????textcl_kp_material_rel???-???????cl_kp_prerequisite_rel????????cl_learning_event???????cl_learning_progress?????cl_learning_session?????cl_student_kp_mastery?????????cl_course????cl_course_chapter?????cl_course_knowledge_rel??-??????cl_course_lesson???int4cl_course_resource?????cl_course_tag?????cl_course_tag_rel??-?????cl_knowledge_point????textcl_kp_material_rel???-???????cl_kp_prerequisite_rel????????cl_learning_event???????cl_learning_progress?????cl_learning_session?????cl_student_kp_mastery?????????cl_course????cl_course_chapter?????cl_course_knowledge_rel??-??????cl_course_lesson???int4cl_course_resource?????cl_course_tag?????cl_course_tag_rel??-?????cl_knowledge_point????textcl_kp_material_rel???-???????cl_kp_prerequisite_rel????????cl_learning_event???????cl_learning_progress?????cl_learning_session?????cl_student_kp_mastery?????????1..n11..n11..n11..n11..n11..n0..11..n11..n11..n11..n11..n0..11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n0..11..n11..n11..n11..n11..n0..11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n0..11..n11..n11..n11..n11..n0..11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n0..11..n11..n11..n11..n11..n0..11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n0..11..n11..n11..n11..n11..n0..11..n11..n11..n11..n11..n11..n1 diff --git a/docs/1/postgres - k12study - question.svg b/docs/1/postgres - k12study - question.svg deleted file mode 100644 index 1381635..0000000 --- a/docs/1/postgres - k12study - question.svg +++ /dev/null @@ -1,128021 +0,0 @@ - - -gd_answer_error_rel??-???????gd_answer_grade????????????/???textgd_answer_kp_analysis??-??????gd_error_tag?????gd_explanation_assessment?????????textgd_explanation_dimension_score?????????textgd_explanation_submission???????int4textgd_grading_task?????gd_review_plan?????gd_score_summary???????int4gd_teacher_comment?????textgd_wrong_question?????hw_assignment???hw_assignment_target???????hw_paper???hw_paper_question??-?????hw_question_bank???hw_question_item??????????texthw_question_kp_rel??-??????hw_submission?????int4hw_submission_answer??????????????????gd_answer_error_rel??-???????gd_answer_grade????????????/???textgd_answer_kp_analysis??-??????gd_error_tag?????gd_explanation_assessment?????????textgd_explanation_dimension_score?????????textgd_explanation_submission???????int4textgd_grading_task?????gd_review_plan?????gd_score_summary???????int4gd_teacher_comment?????textgd_wrong_question?????hw_assignment???hw_assignment_target???????hw_paper???hw_paper_question??-?????hw_question_bank???hw_question_item??????????texthw_question_kp_rel??-??????hw_submission?????int4hw_submission_answer??????????????????gd_answer_error_rel??-???????gd_answer_grade????????????/???textgd_answer_kp_analysis??-??????gd_error_tag?????gd_explanation_assessment?????????textgd_explanation_dimension_score?????????textgd_explanation_submission???????int4textgd_grading_task?????gd_review_plan?????gd_score_summary???????int4gd_teacher_comment?????textgd_wrong_question?????hw_assignment???hw_assignment_target???????hw_paper???hw_paper_question??-?????hw_question_bank???hw_question_item??????????texthw_question_kp_rel??-??????hw_submission?????int4hw_submission_answer??????????????????gd_answer_error_rel??-???????gd_answer_grade????????????/???textgd_answer_kp_analysis??-??????gd_error_tag?????gd_explanation_assessment?????????textgd_explanation_dimension_score?????????textgd_explanation_submission???????int4textgd_grading_task?????gd_review_plan?????gd_score_summary???????int4gd_teacher_comment?????textgd_wrong_question?????hw_assignment???hw_assignment_target???????hw_paper???hw_paper_question??-?????hw_question_bank???hw_question_item??????????texthw_question_kp_rel??-??????hw_submission?????int4hw_submission_answer??????????????????gd_answer_error_rel??-???????gd_answer_grade????????????/???textgd_answer_kp_analysis??-??????gd_error_tag?????gd_explanation_assessment?????????textgd_explanation_dimension_score?????????textgd_explanation_submission???????int4textgd_grading_task?????gd_review_plan?????gd_score_summary???????int4gd_teacher_comment?????textgd_wrong_question?????hw_assignment???hw_assignment_target???????hw_paper???hw_paper_question??-?????hw_question_bank???hw_question_item??????????texthw_question_kp_rel??-??????hw_submission?????int4hw_submission_answer??????????????????1..n11..n11..n11..n11..n11..n11..n11..n0..11..n11..n11..n0..11..n0..11..n0..11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n0..11..n11..n11..n0..11..n0..11..n0..11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n1gd_answer_error_rel??-???????gd_answer_grade????????????/???textgd_answer_kp_analysis??-??????gd_error_tag?????gd_explanation_assessment?????????textgd_explanation_dimension_score?????????textgd_explanation_submission???????int4textgd_grading_task?????gd_review_plan?????gd_score_summary???????int4gd_teacher_comment?????textgd_wrong_question?????hw_assignment???hw_assignment_target???????hw_paper???hw_paper_question??-?????hw_question_bank???hw_question_item??????????texthw_question_kp_rel??-??????hw_submission?????int4hw_submission_answer??????????????????gd_answer_error_rel??-???????gd_answer_grade????????????/???textgd_answer_kp_analysis??-??????gd_error_tag?????gd_explanation_assessment?????????textgd_explanation_dimension_score?????????textgd_explanation_submission???????int4textgd_grading_task?????gd_review_plan?????gd_score_summary???????int4gd_teacher_comment?????textgd_wrong_question?????hw_assignment???hw_assignment_target???????hw_paper???hw_paper_question??-?????hw_question_bank???hw_question_item??????????texthw_question_kp_rel??-??????hw_submission?????int4hw_submission_answer??????????????????gd_answer_error_rel??-???????gd_answer_grade????????????/???textgd_answer_kp_analysis??-??????gd_error_tag?????gd_explanation_assessment?????????textgd_explanation_dimension_score?????????textgd_explanation_submission???????int4textgd_grading_task?????gd_review_plan?????gd_score_summary???????int4gd_teacher_comment?????textgd_wrong_question?????hw_assignment???hw_assignment_target???????hw_paper???hw_paper_question??-?????hw_question_bank???hw_question_item??????????texthw_question_kp_rel??-??????hw_submission?????int4hw_submission_answer??????????????????gd_answer_error_rel??-???????gd_answer_grade????????????/???textgd_answer_kp_analysis??-??????gd_error_tag?????gd_explanation_assessment?????????textgd_explanation_dimension_score?????????textgd_explanation_submission???????int4textgd_grading_task?????gd_review_plan?????gd_score_summary???????int4gd_teacher_comment?????textgd_wrong_question?????hw_assignment???hw_assignment_target???????hw_paper???hw_paper_question??-?????hw_question_bank???hw_question_item??????????texthw_question_kp_rel??-??????hw_submission?????int4hw_submission_answer??????????????????gd_answer_error_rel??-???????gd_answer_grade????????????/???textgd_answer_kp_analysis??-??????gd_error_tag?????gd_explanation_assessment?????????textgd_explanation_dimension_score?????????textgd_explanation_submission???????int4textgd_grading_task?????gd_review_plan?????gd_score_summary???????int4gd_teacher_comment?????textgd_wrong_question?????hw_assignment???hw_assignment_target???????hw_paper???hw_paper_question??-?????hw_question_bank???hw_question_item??????????texthw_question_kp_rel??-??????hw_submission?????int4hw_submission_answer??????????????????gd_answer_error_rel??-???????gd_answer_grade????????????/???textgd_answer_kp_analysis??-??????gd_error_tag?????gd_explanation_assessment?????????textgd_explanation_dimension_score?????????textgd_explanation_submission???????int4textgd_grading_task?????gd_review_plan?????gd_score_summary???????int4gd_teacher_comment?????textgd_wrong_question?????hw_assignment???hw_assignment_target???????hw_paper???hw_paper_question??-?????hw_question_bank???hw_question_item??????????texthw_question_kp_rel??-??????hw_submission?????int4hw_submission_answer??????????????????1..n11..n11..n11..n11..n11..n11..n11..n0..11..n11..n11..n0..11..n0..11..n0..11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n0..11..n11..n11..n0..11..n0..11..n0..11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n0..11..n11..n11..n0..11..n0..11..n0..11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n0..11..n11..n11..n0..11..n0..11..n0..11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n0..11..n11..n11..n0..11..n0..11..n0..11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n11..n1 diff --git a/docs/1/postgres - k12study - recommendation.svg b/docs/1/postgres - k12study - recommendation.svg deleted file mode 100644 index 71fdaba..0000000 --- a/docs/1/postgres - k12study - recommendation.svg +++ /dev/null @@ -1,79021 +0,0 @@ - - -rc_learning_loop_case?????????-??-??-???rc_learning_loop_effect_daily??????????rc_learning_loop_event?????????rc_loop_metric_map?????????????rc_loop_stage_achievement_map??????????????????rc_recommendation_effect_daily????????rc_recommendation_feedback???????rc_recommendation_item???????rc_recommendation_task?????rc_student_profile_feature?????????rc_student_profile_snapshot???????rc_learning_loop_case?????????-??-??-???rc_learning_loop_effect_daily??????????rc_learning_loop_event?????????rc_loop_metric_map?????????????rc_loop_stage_achievement_map??????????????????rc_recommendation_effect_daily????????rc_recommendation_feedback???????rc_recommendation_item???????rc_recommendation_task?????rc_student_profile_feature?????????rc_student_profile_snapshot???????rc_learning_loop_case?????????-??-??-???rc_learning_loop_effect_daily??????????rc_learning_loop_event?????????rc_loop_metric_map?????????????rc_loop_stage_achievement_map??????????????????rc_recommendation_effect_daily????????rc_recommendation_feedback???????rc_recommendation_item???????rc_recommendation_task?????rc_student_profile_feature?????????rc_student_profile_snapshot???????rc_learning_loop_case?????????-??-??-???rc_learning_loop_effect_daily??????????rc_learning_loop_event?????????rc_loop_metric_map?????????????rc_loop_stage_achievement_map??????????????????rc_recommendation_effect_daily????????rc_recommendation_feedback???????rc_recommendation_item???????rc_recommendation_task?????rc_student_profile_feature?????????rc_student_profile_snapshot???????rc_learning_loop_case?????????-??-??-???rc_learning_loop_effect_daily??????????rc_learning_loop_event?????????rc_loop_metric_map?????????????rc_loop_stage_achievement_map??????????????????rc_recommendation_effect_daily????????rc_recommendation_feedback???????rc_recommendation_item???????rc_recommendation_task?????rc_student_profile_feature?????????rc_student_profile_snapshot???????1..n0..11..n0..11..n0..11..n0..11..n11..n11..n11..n11..n0..11..n0..11..n0..11..n0..11..n11..n11..n11..n1rc_learning_loop_case?????????-??-??-???rc_learning_loop_effect_daily??????????rc_learning_loop_event?????????rc_loop_metric_map?????????????rc_loop_stage_achievement_map??????????????????rc_recommendation_effect_daily????????rc_recommendation_feedback???????rc_recommendation_item???????rc_recommendation_task?????rc_student_profile_feature?????????rc_student_profile_snapshot???????rc_learning_loop_case?????????-??-??-???rc_learning_loop_effect_daily??????????rc_learning_loop_event?????????rc_loop_metric_map?????????????rc_loop_stage_achievement_map??????????????????rc_recommendation_effect_daily????????rc_recommendation_feedback???????rc_recommendation_item???????rc_recommendation_task?????rc_student_profile_feature?????????rc_student_profile_snapshot???????rc_learning_loop_case?????????-??-??-???rc_learning_loop_effect_daily??????????rc_learning_loop_event?????????rc_loop_metric_map?????????????rc_loop_stage_achievement_map??????????????????rc_recommendation_effect_daily????????rc_recommendation_feedback???????rc_recommendation_item???????rc_recommendation_task?????rc_student_profile_feature?????????rc_student_profile_snapshot???????rc_learning_loop_case?????????-??-??-???rc_learning_loop_effect_daily??????????rc_learning_loop_event?????????rc_loop_metric_map?????????????rc_loop_stage_achievement_map??????????????????rc_recommendation_effect_daily????????rc_recommendation_feedback???????rc_recommendation_item???????rc_recommendation_task?????rc_student_profile_feature?????????rc_student_profile_snapshot???????rc_learning_loop_case?????????-??-??-???rc_learning_loop_effect_daily??????????rc_learning_loop_event?????????rc_loop_metric_map?????????????rc_loop_stage_achievement_map??????????????????rc_recommendation_effect_daily????????rc_recommendation_feedback???????rc_recommendation_item???????rc_recommendation_task?????rc_student_profile_feature?????????rc_student_profile_snapshot???????rc_learning_loop_case?????????-??-??-???rc_learning_loop_effect_daily??????????rc_learning_loop_event?????????rc_loop_metric_map?????????????rc_loop_stage_achievement_map??????????????????rc_recommendation_effect_daily????????rc_recommendation_feedback???????rc_recommendation_item???????rc_recommendation_task?????rc_student_profile_feature?????????rc_student_profile_snapshot???????1..n0..11..n0..11..n0..11..n0..11..n11..n11..n11..n11..n0..11..n0..11..n0..11..n0..11..n11..n11..n11..n11..n0..11..n0..11..n0..11..n0..11..n11..n11..n11..n11..n0..11..n0..11..n0..11..n0..11..n11..n11..n11..n11..n0..11..n0..11..n0..11..n0..11..n11..n11..n11..n1 diff --git a/docs/architecture.md b/docs/architecture.md index a68c0aa..2808dbc 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,26 +1,127 @@ -# K12Study 项目架构文档(建设中) +# K12Study 项目模块化里程碑(2026-04-13 ~ 2026-07-01) +## 1. 目标与范围 +- 在 7.1 前完成从基础架构到核心业务链路的可联调版本,并进入整体测试封板。 +- 里程碑覆盖全流程:逻辑视图、ER/SQL、基础服务、基础架构 API、基础架构前后端、核心课程/学习业务、模块测试、项目整体测试。 +- 当前项目基线:`gateway + auth + upms + boot-dev + python-ai + frontend + app + init/pg`。 -## 1. 当前已确认技术选型 +## 2. 技术路线(当前版本) +- 图数据库:NebulaGraph(主选),Neo4j(开发备选)。 +- 向量库:Milvus(主选)。 +- 关系库:PostgreSQL(业务主数据与事实源)。 +- 缓存:Redis(token、权限、热点缓存)。 -- 图数据库:NebulaGraph(主选),Neo4j Community(备选) -- 向量库:Milvus -- 关系库:PostgreSQL(存储原始业务表) +## 3. 模块化里程碑分解 +### M0 逻辑视图梳理(04.13-04.15) +- 目标 + - 固化系统分层、边界与主链路,避免后续并行开发职责交叉。 +- 关键任务 + - 统一 BFF/API 网关边界、认证边界、业务域边界、AI 适配边界。 + - 明确 Web / 小程序 / 后端服务 / 数据与中间件的调用方向。 +- 验收产物 + - `docs/architecture/logical-view.md` -## 2. 分层职责(先行约定) +### M1 ER 图与 SQL 设计(04.16-04.21) +- 目标 + - 完成核心业务域数据模型定版,确保接口与代码开发有稳定数据契约。 +- 关键任务 + - 梳理多租户基础域(tenant/dept/user/role/menu)。 + - 梳理课程域、题目与批改域、成就与推荐域、AI 知识域的数据关系。 + - 定义 SQL 执行顺序、索引策略、跨 schema 依赖约束。 +- 验收产物 + - `docs/architecture/er-diagram.md` + - `docs/architecture/schema-baseline.sql` -- PostgreSQL:业务主数据与原始表数据(唯一事实源) -- NebulaGraph / Neo4j:知识点、题目、学生作答、知识关系等图结构查询 -- Milvus:教材/题解/知识片段向量检索与语义召回 +### M2 基础服务配置(04.22-04.26) +- 目标 + - 打通基础运行底座:PostgreSQL、Redis、注册中心、知识图谱、向量库。 +- 关键任务 + - 统一本地开发环境变量命名与连接方式。 + - 约定 NebulaGraph(主)/Neo4j(备)与 Milvus 接入规范。 + - 明确健康检查、持久化卷、网络隔离策略。 +- 验收产物 + - `docs/architecture/base-services.md` + - `docs/architecture/service-baseline.compose.yaml` -## 3. 三库协同原则(首版) +### M3 基础架构 API 定义(04.27-05.03) +- 目标 + - 完成基础能力 API(认证、权限、租户、组织、路由、基础文件)契约冻结。 +- 关键任务 + - 统一响应结构、鉴权方式、错误码与 traceId 规范。 + - 固化 auth/upms 的 API 清单与扩展位。 +- 验收产物 + - `docs/architecture/api-design.md`(基础架构 API 段) -- 关系库到图库:通过同步任务进行实体与关系映射 -- 关系库到向量库:通过切片与 embedding 任务进行索引 -- 图谱节点保留源表标识(如 `source_table`、`source_pk`),支持点击节点回查原始表数据 +### M4 基础架构前后端代码构建(05.04-05.10) +- 目标 + - 实现并联调基础架构链路(登录、当前用户、动态路由、区域树、租户树、部门树)。 +- 关键任务 + - 后端:auth/upms 与网关链路稳定化。 + - 前端:React 管理端基础框架与动态路由消费。 + - 小程序:基础请求层与最小页面壳可访问基础接口。 +- 验收标准 + - `/api/auth/*`、`/api/upms/*` 在本地模式和网关模式都可联调。 -## 4. 架构图产物(待补充) +### M5 核心课程/学习业务 API 与数据模型冻结(05.11-05.24) +- 目标 + - 将剩余周期聚焦课程与学习核心内容,完成核心域契约冻结。 +- 关键任务 + - 完成课程、学习路径、学习记录、学习效果等核心 API 与数据模型定义。 + - 固化核心流程输入输出 DTO 与事件流。 +- 验收产物 + - `docs/architecture/api-design.md`(课程/学习核心段) -- 系统流程图:建设中 -- ER 图:建设中 -- 数据流图:建设中 +### M6 核心课程/学习业务前后端构建(05.25-06.14) +- 目标 + - 完成课程与学习核心功能前后端落地并联调。 +- 关键任务 + - 后端:课程与学习域核心能力实现。 + - 前端/小程序:课程学习主流程页面与 API 接入。 +- 验收标准 + - 课程发布 -> 学习执行 -> 学习记录回写的核心闭环可演示。 + +### M7 核心内容专项测试与修复(06.15-06.24) +- 目标 + - 核心课程/学习模块通过功能、接口、回归测试。 +- 关键任务 + - 覆盖课程、学习、基础架构联动场景。 + - 修复阻塞级问题并完成回归。 +- 验收产物 + - `docs/architecture/test-plan.md`(模块测试段) + +### M8 整体回归与发布评估(06.25-07.01) +- 目标 + - 完成全链路回归、性能基线与发布准入评估。 +- 关键任务 + - 核心课程/学习主流程端到端回归与异常链路验证。 + - 输出上线前 go/no-go 结论。 +- 验收产物 + - `docs/architecture/test-plan.md`(整体测试段) + +## 4. 三库协同原则(首版) +- 关系库到图库:通过同步任务进行实体与关系映射。 +- 关系库到向量库:通过切片与 embedding 任务进行索引。 +- 图谱节点保留源表标识(如 `source_table`、`source_pk`),支持点击节点回查原始表数据。 + +## 5. 执行原则 +- 所有模块按“先契约、后实现、再联调、最后回归”的顺序推进。 +- API 契约冻结后,跨端协作不随意破坏兼容性;新增字段走向后兼容策略。 +- 数据模型以 `init/pg` 既有结构为基础,新增结构优先走增量脚本。 + +## 6. 风险与缓冲策略 +- 风险一:业务域并行开发导致接口反复变更。 + - 策略:M3/M5 设置 API 冻结点,冻结后变更必须评审。 +- 风险二:AI 相关链路依赖图谱/向量服务稳定性不足。 + - 策略:先完成本地 mock + 异步任务队列,再切换真实存储。 +- 风险三:最后阶段联调问题集中爆发。 + - 策略:M6 结束前必须打通最小闭环,M7 提前做模块回归。 + +## 7. 分册文档 +- `docs/architecture/README.md` +- `docs/architecture/logical-view.md` +- `docs/architecture/er-diagram.md` +- `docs/architecture/schema-baseline.sql` +- `docs/architecture/base-services.md` +- `docs/architecture/service-baseline.compose.yaml` +- `docs/architecture/api-design.md` +- `docs/architecture/test-plan.md` diff --git a/docs/architecture/README.md b/docs/architecture/README.md new file mode 100644 index 0000000..86c284c --- /dev/null +++ b/docs/architecture/README.md @@ -0,0 +1,30 @@ +# K12Study 架构设计分册 +## 文档目标 +本目录用于承载 4.13-7.1 里程碑执行所需的架构设计产物,作为 `docs/architecture.md` 的细化文档集合。 + +## 文档清单 +- `logical-view.md` + - 系统逻辑视图、模块边界、主链路。 +- `er-diagram.md` + - 核心业务 ER 关系与跨域数据依赖说明。 +- `schema-baseline.sql` + - 数据与 AI 存储侧的增量 SQL 草案(设计基线)。 +- `base-services.md` + - PG/Redis/知识图谱/向量库基础服务配置方案。 +- `service-baseline.compose.yaml` + - 基础服务编排样例(开发环境基线)。 +- `api-design.md` + - 基础架构 API 与业务功能 API 设计。 +- `test-plan.md` + - 模块测试和项目整体测试计划。 +- `系统架构.drawio` + - 系统分层、角色入口、Java/Python/数据层关系图。 +- `代码架构.drawio` + - 前后端与服务模块、共享契约、基础设施代码架构图。 + +## 使用顺序 +1. 先阅读 `logical-view.md` 明确边界。 +2. 再阅读 `er-diagram.md` 与 `schema-baseline.sql` 对齐数据模型。 +3. 按 `base-services.md` 与 `service-baseline.compose.yaml` 准备基础服务。 +4. 依据 `api-design.md` 先冻结契约,再推进前后端实现。 +5. 按 `test-plan.md` 执行模块测试与整体测试。 diff --git a/docs/architecture/api-design.md b/docs/architecture/api-design.md new file mode 100644 index 0000000..1a139fb --- /dev/null +++ b/docs/architecture/api-design.md @@ -0,0 +1,82 @@ +# API 设计(基础架构 + 业务功能) +## 1. API 设计原则 +- 对外统一前缀:`/api/*`。 +- 统一响应结构:`code/message/data/traceId`。 +- 认证策略:JWT + RBAC,网关做统一鉴权透传。 +- API 冻结点: + - 基础架构 API 在 M3 冻结。 + - 业务 API 在 M5 冻结。 + +## 2. 基础架构 API(M3) +### 2.1 认证域(auth) +- `POST /api/auth/login` +- `POST /api/auth/refresh` +- `GET /api/auth/current-user` + +### 2.2 权限与组织域(upms) +- `GET /api/upms/routes` +- `GET /api/upms/current-user` +- `GET /api/upms/areas/tree` +- `GET /api/upms/tenants/tree` +- `GET /api/upms/depts/tree` + +### 2.3 基础扩展 API(建议补充) +- 文件域 + - `POST /api/upms/files/upload` + - `GET /api/upms/files/{fileId}` +- 站内消息域 + - `GET /api/upms/messages/inbox` + - `POST /api/upms/messages/{messageId}/read` + +## 3. 业务功能 API(M5) +### 3.1 课程域(course) +- `POST /api/course/courses` +- `GET /api/course/courses/{courseId}` +- `POST /api/course/courses/{courseId}/chapters` +- `POST /api/course/knowledge-points` +- `POST /api/course/chapters/{chapterId}/knowledge-relations` +- `GET /api/course/students/{studentId}/mastery` + +### 3.2 习题/作业/批改域(question) +- 题库与题目 + - `POST /api/question/banks` + - `POST /api/question/questions` + - `POST /api/question/banks/{bankId}/questions/{questionId}` +- 试卷与作业 + - `POST /api/question/papers` + - `POST /api/question/assignments` + - `POST /api/question/assignments/{assignmentId}/targets` +- 提交与批改 + - `POST /api/question/submissions` + - `POST /api/question/submissions/{submissionId}/answers` + - `POST /api/question/grading/tasks` + - `GET /api/question/grading/tasks/{taskId}` +- 错题与复习 + - `GET /api/question/wrong-questions` + - `POST /api/question/review/plans/generate` + - `POST /api/question/review/executions` + +### 3.3 成就域(achievement) +- `POST /api/achievement/definitions` +- `POST /api/achievement/rules/templates` +- `GET /api/achievement/users/{userId}/records` +- `GET /api/achievement/users/{userId}/progress` +- `POST /api/achievement/events/trigger` + +### 3.4 推荐域(recommendation) +- `POST /api/recommendation/tasks` +- `GET /api/recommendation/tasks/{taskId}/items` +- `POST /api/recommendation/items/{itemId}/feedback` +- `GET /api/recommendation/effects/daily` + +### 3.5 AI 知识域(ai) +- `POST /api/ai/knowledge/files` +- `POST /api/ai/knowledge/files/{fileId}/sync` +- `GET /api/ai/knowledge/sync-tasks/{taskId}` +- `POST /api/ai/retrieval/query` +- `GET /api/ai/health` + +## 4. API 阶段交付映射 +- M3:`auth + upms + file/message 基础扩展` +- M5:`course + question + achievement + recommendation + ai` +- M6:前后端按冻结 API 完成联调,不新增破坏性变更。 diff --git a/docs/architecture/base-services.md b/docs/architecture/base-services.md new file mode 100644 index 0000000..f2bc2f1 --- /dev/null +++ b/docs/architecture/base-services.md @@ -0,0 +1,59 @@ +# 基础服务配置方案(PG / Redis / 知识图谱 / 向量库) +## 1. 目标 +- 为 4.13-7.1 研发阶段提供统一、可重复的基础服务底座。 +- 与现有 `docker/dev-common` 与 `.devcontainer` 方案兼容,减少环境差异。 + +## 2. 服务基线 +- 必选 + - PostgreSQL:主业务数据存储(多 schema)。 + - Redis:缓存、会话、热点数据。 + - Nacos:配置中心与注册中心(服务化阶段)。 + - NebulaGraph:知识图谱主方案(Neo4j 作为开发备选)。 + - Milvus:向量检索主方案。 +- 可选 + - Python AI 服务:统一模型调用、图谱同步、向量写入。 + +## 3. 环境变量命名建议 +- PostgreSQL + - `K12STUDY_DB_HOST` + - `K12STUDY_DB_PORT` + - `K12STUDY_DB_NAME` + - `K12STUDY_DB_USER` + - `K12STUDY_DB_PASSWORD` +- Redis + - `K12STUDY_REDIS_HOST` + - `K12STUDY_REDIS_PORT` + - `K12STUDY_REDIS_PASSWORD` +- Nacos + - `K12STUDY_REGISTRY_HOST` + - `K12STUDY_REGISTRY_PORT` + - `K12STUDY_REGISTRY_GRPC_PORT` + - `K12STUDY_REGISTRY_RAFT_PORT` +- 知识图谱(Nebula) + - `K12STUDY_GRAPH_HOST` + - `K12STUDY_GRAPH_PORT` + - `K12STUDY_GRAPH_USER` + - `K12STUDY_GRAPH_PASSWORD` +- 向量库(Milvus) + - `K12STUDY_VECTOR_HOST` + - `K12STUDY_VECTOR_PORT` + - `K12STUDY_VECTOR_GRPC_PORT` + +## 4. 配置落地建议 +- 阶段一(M2) + - 先以本地单机编排启动,保证联调可用。 +- 阶段二(M5-M6) + - 将图谱/向量配置接入 `ai-client` 与 `python-ai`。 +- 阶段三(M7-M8) + - 增加连通性与降级测试(图谱不可用、向量库超时、Redis 抖动)。 + +## 5. 健康检查建议 +- PostgreSQL:`pg_isready` +- Redis:`redis-cli ping` +- NebulaGraph:`graphd` 端口连接可用 +- Milvus:HTTP `/healthz` +- Python AI:`GET /health` + +## 6. 与现有目录关系 +- 现有公共服务编排:`docker/dev-common/docker-compose.public-services.yml` +- 本文新增服务编排样例:`docs/architecture/service-baseline.compose.yaml` diff --git a/docs/architecture/er-diagram.md b/docs/architecture/er-diagram.md new file mode 100644 index 0000000..2e774bc --- /dev/null +++ b/docs/architecture/er-diagram.md @@ -0,0 +1,77 @@ +# K12Study 核心 ER 设计 +## 1. 建模范围 +- 基础域:租户、组织、用户、角色、菜单、文件。 +- 课程域:课程、章节、学习节点、知识点、学习进度。 +- 习题与批改域:题库、试卷、作业、提交、批改、错题与复习。 +- 成就与推荐域:成就定义、用户成就、推荐任务、反馈、学习闭环。 +- AI 知识域:知识文件、同步任务、图谱实体/关系。 + +## 2. 核心关系图(基础域 + 教学主链路) +```mermaid +erDiagram + SYS_TENANT ||--o{ SYS_DEPT : contains + SYS_DEPT ||--o{ SYS_USER : has + SYS_ROLE ||--o{ SYS_ROLE_MENU : grants + SYS_MENU ||--o{ SYS_ROLE_MENU : mapped + SYS_USER ||--o{ SYS_FILE : uploads + + COURSE ||--o{ COURSE_CHAPTER : contains + COURSE_CHAPTER ||--o{ COURSE_NODE : contains + KNOWLEDGE_POINT ||--o{ CHAPTER_KP_REL : mapped + COURSE_CHAPTER ||--o{ CHAPTER_KP_REL : mapped + COURSE_NODE ||--o{ NODE_KP_REL : mapped + KNOWLEDGE_POINT ||--o{ NODE_KP_REL : mapped + + QUESTION_BANK ||--o{ BANK_QUESTION_REL : contains + QUESTION_ITEM ||--o{ BANK_QUESTION_REL : included + PAPER ||--o{ PAPER_QUESTION : contains + QUESTION_ITEM ||--o{ PAPER_QUESTION : included + ASSIGNMENT ||--o{ SUBMISSION : receives + SUBMISSION ||--o{ SUBMISSION_ANSWER : has + QUESTION_ITEM ||--o{ SUBMISSION_ANSWER : answered + GRADING_TASK ||--o{ ANSWER_GRADE : produces + SUBMISSION_ANSWER ||--o{ ANSWER_GRADE : graded + + USER_ACHIEVEMENT }o--|| ACHIEVEMENT : awarded + USER_ACHIEVEMENT }o--|| SYS_USER : owner + RECOMMENDATION_TASK ||--o{ RECOMMENDATION_ITEM : produces + RECOMMENDATION_ITEM ||--o{ RECOMMENDATION_FEEDBACK : receives +``` + +## 3. AI 知识域关系图 +```mermaid +erDiagram + SYS_FILE ||--o{ AI_KNOWLEDGE_FILE : source + AI_KNOWLEDGE_FILE ||--o{ AI_KNOWLEDGE_SYNC_TASK : schedules + AI_KNOWLEDGE_FILE ||--o{ AI_GRAPH_ENTITY : extracts + AI_GRAPH_ENTITY ||--o{ AI_GRAPH_RELATION : links +``` + +## 4. 实体到现有表映射(节选) +- `SYS_TENANT` -> `upms.tb_sys_tenant` +- `SYS_DEPT` -> `upms.tb_sys_dept` +- `SYS_USER` -> `upms.tb_sys_user` +- `SYS_ROLE` -> `upms.tb_sys_role` +- `SYS_MENU` -> `upms.tb_sys_menu` +- `COURSE` -> `course.cl_course` +- `KNOWLEDGE_POINT` -> `course.cl_knowledge_point` +- `QUESTION_ITEM` -> `question.hw_question_item` +- `ASSIGNMENT` -> `question.hw_assignment` +- `SUBMISSION` -> `question.hw_submission` +- `GRADING_TASK` -> `question.gd_grading_task` +- `ACHIEVEMENT` -> `achievement.ac_achievement` +- `USER_ACHIEVEMENT` -> `achievement.ac_user_achievement` +- `AI_KNOWLEDGE_FILE` -> `ai.tb_ai_knowledge_file` + +## 5. SQL 基线目录(现有) +- `init/pg/auth/10_create_auth_tables.sql` +- `init/pg/upms/10_create_upms_tables.sql` +- `init/pg/course/10_create_course_tables.sql` +- `init/pg/question/10_create_question_tables.sql` +- `init/pg/achievement/10_create_achievement_tables.sql` +- `init/pg/ai/10_create_ai_tables.sql` + +## 6. 设计建议 +- 保持跨域依赖单向:`course/question/achievement/recommendation` 通过 `upms` 提供的主体信息关联。 +- 涉及 AI 知识检索的数据优先走异步同步表,避免在线写链路阻塞。 +- 复杂业务报表优先通过事实表/汇总表落地,避免线上实时多表大 join。 diff --git a/docs/architecture/logical-view.md b/docs/architecture/logical-view.md new file mode 100644 index 0000000..6e2e0e3 --- /dev/null +++ b/docs/architecture/logical-view.md @@ -0,0 +1,77 @@ +# K12Study 逻辑视图 +## 1. 分层结构 +- 终端层 + - `frontend`(React 管理端) + - `app`(微信小程序) +- 接入与安全层 + - `gateway`(统一入口、鉴权、路由、跨域、trace) + - `auth`(登录、刷新、当前用户) +- 平台能力层 + - `upms`(租户、组织、用户、角色、菜单、路由元数据) + - `ai-client`(Java 侧 AI 适配) + - `python-ai`(AI 处理服务) +- 业务域层(规划中) + - `course`、`question`、`achievement`、`recommendation` +- 数据与基础设施层 + - PostgreSQL(多 schema) + - Redis(缓存、会话、热点) + - NebulaGraph / Neo4j(知识图谱) + - Milvus(向量检索) + - Nacos(注册配置) + +## 2. 逻辑架构图 +```mermaid +graph TD + WEB[Frontend React] --> GW[Gateway] + MINI[Mini Program] --> GW + + GW --> AUTH[Auth Service] + GW --> UPMS[UPMS Service] + GW --> COURSE[Course Service] + GW --> QUESTION[Question Service] + GW --> ACHIEVE[Achievement Service] + GW --> REC[Recommendation Service] + + AUTH --> PG[(PostgreSQL)] + UPMS --> PG + COURSE --> PG + QUESTION --> PG + ACHIEVE --> PG + REC --> PG + + AUTH --> REDIS[(Redis)] + UPMS --> REDIS + COURSE --> REDIS + QUESTION --> REDIS + ACHIEVE --> REDIS + REC --> REDIS + + QUESTION --> AICLIENT[AI Client] + AICLIENT --> PYAI[Python AI] + PYAI --> KG[(NebulaGraph / Neo4j)] + PYAI --> VDB[(Milvus)] + PYAI --> PG +``` + +## 3. 主链路视角 +### 3.1 基础架构链路 +- 登录:`frontend/app -> /api/auth/login -> auth -> token` +- 权限与路由:`frontend/app -> /api/upms/routes -> upms` +- 组织数据:`/api/upms/areas/tree|tenants/tree|depts/tree` + +### 3.2 教学业务链路 +- 教师发作业:题库/试卷/作业配置 -> 投放班级。 +- 学生提交:答题内容 + 文件(可选)。 +- 批改流程:自动批改 + 人工复核 -> 成绩汇总 -> 错题沉淀。 +- 复习与推荐:复习计划 -> 推荐内容 -> 用户反馈 -> 闭环效果。 +- 成就激励:事件触发 -> 规则命中 -> 成就发放/进度更新。 + +### 3.3 AI 知识链路 +- 文件入库:`upms.tb_sys_file -> ai.tb_ai_knowledge_file` +- 同步任务:`ai.tb_ai_knowledge_sync_task` 异步推送图谱/向量库 +- 检索与推理:业务服务通过 `ai-client` 调 `python-ai` + +## 4. 设计约束 +- API 对外统一前缀 `/api/*`,内部服务前缀保持领域语义。 +- 多租户与组织隔离字段在核心表统一保留(`tenant_id/tenant_path/dept_id/dept_path/adcode`)。 +- 业务域之间优先通过 API 契约与事件解耦,不直接跨模块读写表。 diff --git a/docs/architecture/modules/01-课程学习/er图.drawio b/docs/architecture/modules/01-课程学习/er图.drawio index 0a05588..d8c4435 100644 --- a/docs/architecture/modules/01-课程学习/er图.drawio +++ b/docs/architecture/modules/01-课程学习/er图.drawio @@ -13,7 +13,7 @@ - + @@ -22,7 +22,7 @@ - + @@ -34,7 +34,7 @@ - + @@ -46,15 +46,18 @@ - + - + + + + @@ -88,13 +91,13 @@ - + - + @@ -103,6 +106,9 @@ + + + diff --git a/docs/architecture/modules/01-课程学习/数据流图.drawio b/docs/architecture/modules/01-课程学习/数据流图.drawio index 3b6884f..395501d 100644 --- a/docs/architecture/modules/01-课程学习/数据流图.drawio +++ b/docs/architecture/modules/01-课程学习/数据流图.drawio @@ -31,7 +31,7 @@ - + diff --git a/docs/architecture/modules/02-习题与作业/er图.drawio b/docs/architecture/modules/02-习题与作业/er图.drawio index e1647c4..1c58617 100644 --- a/docs/architecture/modules/02-习题与作业/er图.drawio +++ b/docs/architecture/modules/02-习题与作业/er图.drawio @@ -7,34 +7,37 @@ - + - + + + + - + - + - + @@ -43,7 +46,8 @@ - + + diff --git a/docs/architecture/modules/03-批改与反馈/er图.drawio b/docs/architecture/modules/03-批改与反馈/er图.drawio index 587480b..7e5c2e7 100644 --- a/docs/architecture/modules/03-批改与反馈/er图.drawio +++ b/docs/architecture/modules/03-批改与反馈/er图.drawio @@ -28,13 +28,19 @@ - + + + + + + + - + @@ -43,7 +49,7 @@ - + @@ -88,6 +94,12 @@ + + + + + + diff --git a/docs/architecture/modules/03-批改与反馈/数据流图.drawio b/docs/architecture/modules/03-批改与反馈/数据流图.drawio index 3e77393..ab764a7 100644 --- a/docs/architecture/modules/03-批改与反馈/数据流图.drawio +++ b/docs/architecture/modules/03-批改与反馈/数据流图.drawio @@ -24,17 +24,17 @@ - - - + + + - + - + @@ -46,14 +46,14 @@ - - + + - + diff --git a/docs/architecture/代码架构.drawio b/docs/architecture/代码架构.drawio new file mode 100644 index 0000000..a1ddfea --- /dev/null +++ b/docs/architecture/代码架构.drawio @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/architecture/完整业务流程图.drawio b/docs/architecture/完整业务流程图.drawio index f8ceb05..4bea98f 100644 --- a/docs/architecture/完整业务流程图.drawio +++ b/docs/architecture/完整业务流程图.drawio @@ -1,6 +1,6 @@ - + @@ -13,16 +13,16 @@ - + - + - + @@ -31,17 +31,17 @@ - - + + - + - + - + @@ -49,7 +49,7 @@ - + @@ -61,7 +61,7 @@ - + @@ -85,65 +85,65 @@ - + - + - + - + - - + + - - + + - - + + - - + + - - + + - + - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + @@ -190,7 +190,7 @@ - + @@ -217,7 +217,7 @@ - + @@ -253,6 +253,15 @@ + + + + + + + + + diff --git a/docs/architecture/数据流图(多角色).drawio b/docs/architecture/数据流图(多角色).drawio index 117398c..f620364 100644 --- a/docs/architecture/数据流图(多角色).drawio +++ b/docs/architecture/数据流图(多角色).drawio @@ -1,130 +1,202 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/architecture/系统架构.drawio b/docs/architecture/系统架构.drawio new file mode 100644 index 0000000..d4ecfaf --- /dev/null +++ b/docs/architecture/系统架构.drawio @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/architecture/系统架构与代码架构.drawio b/docs/architecture/系统架构与代码架构.drawio deleted file mode 100644 index 4406aa3..0000000 --- a/docs/architecture/系统架构与代码架构.drawio +++ /dev/null @@ -1,450 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/milestone.md b/docs/milestone.md new file mode 100644 index 0000000..d4eb365 --- /dev/null +++ b/docs/milestone.md @@ -0,0 +1,81 @@ +# K12Study 项目里程碑(4.13-7.1) +## 1. 目标 +- 在 7.1 前完成从基础架构到业务闭环的可交付版本。 +- 形成“可开发、可联调、可测试、可发布”的完整流程。 + +## 2. 里程碑总览 +### M0 逻辑视图梳理(04.13-04.15) +- 产出 + - 逻辑分层、模块边界、核心调用链路。 +- 完成标准 + - 前后端、网关、服务、数据层职责边界确认。 + +### M1 ER 图与 SQL 设计(04.16-04.21) +- 产出 + - 核心域 ER 关系与 SQL 基线。 +- 完成标准 + - 关键业务实体关系可支持后续 API 定义与实现。 + +### M2 基础服务配置(04.22-04.26) +- 产出 + - PostgreSQL、Redis、Nacos、知识图谱、向量库基础编排。 +- 完成标准 + - 本地环境可稳定启动并通过健康检查。 + +### M3 基础架构 API 定义(04.27-05.03) +- 产出 + - 认证、权限、组织、动态路由 API 契约冻结。 +- 完成标准 + - API 输入输出、鉴权策略、错误码规范完成评审。 + +### M4 基础架构前后端构建(05.04-05.10) +- 产出 + - auth/upms/gateway 联调通过,前端与小程序可调用基础 API。 +- 完成标准 + - 登录、当前用户、动态路由、组织树查询链路可演示。 + +### M5 核心课程/学习业务 API 与模型冻结(05.11-05.17) +- 产出 + - 课程、学习路径、学习记录、学习效果相关 API 与数据模型冻结。 +- 完成标准 + - 核心课程/学习域接口契约可直接指导开发实现。 + +### M6 核心课程/学习业务前后端构建(05.18-06.14) +- 产出 + - 课程与学习主流程前后端实现并完成联调。 +- 完成标准 + - 课程发布 -> 学习执行 -> 学习记录回写闭环可运行。 + +### M7 核心内容专项测试与修复(06.15-06.24) +- 产出 + - 课程/学习相关模块测试报告与修复记录。 +- 完成标准 + - 阻塞级问题清理并完成修复回归。 + +### M8 全链路回归与发布评估(06.25-07.01) +- 产出 + - 端到端测试结果、发布准入结论(Go/No-Go)。 +- 完成标准 + - 核心主流程通过,具备发布条件。 + +## 3. 关键交付物映射 +- 架构与设计 + - `docs/architecture.md` + - `docs/architecture/logical-view.md` + - `docs/architecture/er-diagram.md` + - `docs/architecture/schema-baseline.sql` +- 服务与环境 + - `docker/dev-common/docker-compose.yml` + - `docker/dev-common/docker-compose.public-services.yml` + - `.devcontainer/external/docker-compose.yml` + - `.devcontainer/internal/docker-compose.yml` +- API 与测试 + - `docs/architecture/api-design.md` + - `docs/architecture/test-plan.md` + +## 4. 发布前检查清单 +- API 契约无破坏性变更。 +- 基础服务编排可稳定启动。 +- 核心闭环 E2E 全通过。 +- P0/P1 缺陷清零。 +- 关键指标可观测(错误率、超时率、任务积压)。 diff --git a/docs/plan/architecture.md b/docs/plan/architecture.md deleted file mode 100644 index 2eb111a..0000000 --- a/docs/plan/architecture.md +++ /dev/null @@ -1,205 +0,0 @@ -# K12Study 首版项目框架规划 - -## Progress - -- [x] 根目录骨架已创建:`backend`、`frontend`、`app`、`init/pg` -- [x] `backend` Maven 多模块目录与基础 POM 已落地 -- [x] `gateway`、`auth`、`upms`、`boot-dev`、`python-ai` 首版占位代码已创建 -- [x] `init/pg` 已按模块拆分,并接入根目录 `tb_sys_area.sql` -- [x] 学校租户表命名已修正为 `tb_sys_tenant` -- [x] 跨包 DTO、Enums 已收口到对应 `api-*` 模块 -- [x] Web 端已从 workspace 收敛为单一 React 项目 -- [x] Web 端请求层已切换为原生 `fetch` 封装 -- [x] 根目录已新增独立 `app` 微信小程序骨架 -- [x] VS Code `docker-dev` 双模式已补齐:`external` 直连外部 PG/Redis,`internal` 内置 PG/Redis -- [x] `backend` Maven 聚合编译、`frontend` 构建、`python-ai` 语法校验已通过 -- [x] `boot-dev` 本地模式烟测已通过,`/api/auth/login`、`/api/upms/routes`、`/api/actuator/health` 可访问 -- [ ] 下一步继续补真实持久化、分片路由封装与更完整联调 - -## Summary - -- 根目录固定为: - - `backend` - - `frontend` - - `app` -- 参考 `Tik / urbanLifeline` 的模块拆分和基础层组织,但不复用 `Dubbo`、`Vue` 和旧业务实现。 -- 首版目标是“可运行骨架”: - - 后端:`Spring Boot + Spring Cloud Gateway + Spring RESTful + JWT + RBAC + MyBatis-Plus + PostgreSQL + Redis` - - Web:单一 `React` 项目,只保留 `api / types / utils / components / dynamic layout / dynamic route` - - App:独立微信小程序骨架 - - AI:独立 `Python` 服务占位 -- 本地开发采用双模式: - - 分布式目录与服务边界长期保留 - - `boot-dev` 聚合模块一键启动主要 Java 能力 -- `/api` 只属于 `gateway` 和 `boot-dev` 的外层上下文,不属于子服务自身前缀。 - -## Key Changes - -- `backend` 采用 Maven 多模块,固定为: - - `backend/common`:`common-core`、`common-web`、`common-security`、`common-mybatis`、`common-redis`、`common-api` - - `backend/apis`:`api-auth`、`api-upms`、`api-ai` - - `backend/gateway`:统一入口、鉴权、路由、跨域、trace 透传 - - `backend/auth`:登录、token、当前用户 - - `backend/upms`:用户、角色、菜单授权、菜单与动态路由元数据、组织与区域基座 - - `backend/ai-client`:Java 调 Python 的适配层 - - `backend/boot-dev`:本地聚合启动模块 - - `backend/python-ai`:独立 Python AI 服务占位 -- 服务通信固定为 `REST`,不使用 `Dubbo` -- 认证固定为 `JWT + RBAC` - - `gateway` 统一验签 - - 下游服务走网关信任模式 -- 数据访问固定为 `MyBatis-Plus + PostgreSQL + Redis` - - PostgreSQL 预留分库分表、分区、读写分离扩展位 - - Redis 负责 token、权限缓存、动态路由缓存、热点数据 -- `frontend` 固定为单一 React 项目: - - `frontend/src/api` - - `frontend/src/types` - - `frontend/src/utils` - - `frontend/src/components` - - `frontend/src/layouts` - - `frontend/src/router` - - 只保留基础壳与底层能力,不做业务页面堆砌 -- `app` 固定为根目录独立微信小程序工程: - - `app/src/app.*` - - `app/src/pages/*` - - `app/src/api` - - `app/src/utils` -- `boot-dev` 本地模式固定策略: - - 保持和分布式服务同样的模块结构与接口边界 - - 本地可单进程启动 - - 可用一个 PostgreSQL 实例承载所有逻辑分片,但字段、路由键、表结构必须与未来分布式模式一致 - -## Region / Tenant Model - -- 系统是“总校 -> 省级分校 -> 市区分校”的多层级租户结构 -- 每个校区下再有部门,当前部门维度至少支持“年级、学科”等业务组织 -- 区域是分库分表的核心路由维度: - - 以“省份区域”作为首要分片依据 - - 业务表设计时必须显式保留区域路由字段 -- `tb_sys_area.sql` 视为区域基础数据来源约束: - - 首版必须预留 `tb_sys_area` 基础表和初始化脚本接入位 - - 区域编码、层级、父子关系以 `tb_sys_area.sql` 为准 -- 首版数据模型明确区分两棵树: - - 区域树:省 / 市 / 区县 - - 组织树:总校 / 分校 / 校区 / 部门 -- 首版 `upms` 基础对象固定包含: - - `SysArea` - - `SysTenant` - - `SysDept` - - `SysUser` - - `SysRole` -- 所有租户级业务主表统一预留字段: - - `adcode` - - `tenant_id` 或 `school_id` - - `tenant_path` - - `dept_id` - - `dept_path` -- 路由与隔离规则固定: - - 区域字段负责数据库路由与物理分片 - - `tenant_path` / `dept_path` 负责组织级数据隔离 - - 不允许只靠 `dept_path` 承担全部多租户职责 - -## Database Init Layout - -- PostgreSQL 初始化脚本固定放在 `init` 目录下,且按模块独立管理 -- 目录结构固定为类似: - - `init/pg/00_create_db.sql` - - `init/pg/01_create_schema.sql` - - `init/pg/sys/tb_sys_area.sql` - - `init/pg/auth/*.sql` - - `init/pg/upms/*.sql` - - `init/pg/ai/*.sql` -- 原则固定: - - 每个模块维护自己的建表 SQL、索引 SQL、初始化数据 SQL - - 不把所有表混在一个超大 SQL 文件中 - - 公共基础表单独归 `sys` 或 `common` 目录 -- `tb_sys_area.sql` 固定归属: - - 放在 `init/pg/sys/` - - 作为区域基础数据的首批初始化脚本 -- 模块 SQL 的职责边界固定: - - `auth`:登录、token、认证相关表 - - `upms`:用户、角色、菜单、角色菜单授权、学校租户、部门、区域引用关系 - - `ai`:AI 调用记录、任务记录、模型配置占位 -- 初始化脚本执行规则固定: - - 先执行库 / Schema 基础脚本 - - 再执行 `sys` - - 再执行各业务模块 - - 各模块内部按 `create table -> index -> init data` 顺序组织 -- 本地开发模式固定: - - `boot-dev` 使用同一套 `init/pg` 脚本 - - 即使单库启动,也不能做一套“临时简化 SQL”绕过正式字段设计 - -## Public APIs / Interfaces - -- 外部访问前缀固定为: - - `/api/auth/**` - - `/api/upms/**` - - `/api/actuator/health` 或 `/actuator/**` -- 子服务内部前缀固定为: - - `auth`:`/auth/**` - - `upms`:`/upms/**` -- 路由映射固定为: - - `gateway: /api/auth/** -> auth: /auth/**` - - `gateway: /api/upms/** -> upms: /upms/**` - - `boot-dev` 本地聚合模式下同样暴露 `/api/auth/**`、`/api/upms/**` -- 统一响应结构固定为: - - `code` - - `message` - - `data` - - `traceId` -- `upms` 首版接口能力必须覆盖: - - 当前用户信息 - - 用户 / 角色 / 菜单授权基座 - - 区域树查询 - - 学校租户树查询 - - 部门树查询 - - 动态路由元数据查询 -- React 动态路由元数据至少包含: - - `id` - - `path` - - `name` - - `component` - - `layout` - - `children` - - `meta` -- Python AI 服务首版只固定: - - `GET /health` - -## VS Code Docker Dev - -- 支持两种开发模式: - - `external`:PG、Redis 等直连外部数据库 - - `internal`:开发容器内部创建 PG、Redis -- 代码同步方式为 bind mount: - - 宿主机目录直接挂载到容器工作目录 - - 本地修改会实时反映到容器内 - - 不依赖额外“同步脚本”复制代码 - -## Test Plan - -- `backend` 根级 Maven 聚合可编译通过 -- `backend/boot-dev` 可单进程启动,并对外暴露 `/api/auth/**`、`/api/upms/**` -- `backend/gateway` 可独立启动并完成到 `auth`、`upms` 的路由转发 -- JWT 鉴权链路可烟测通过 -- `frontend` 单项目可完成 `install`、`dev`、`build` -- React 端可基于 `upms` 返回的动态路由元数据完成路由挂载 -- `app` 可用微信开发者工具直接打开骨架工程 -- `backend/python-ai` 可独立启动并通过 `/health` -- PostgreSQL 与 Redis 本地联调配置可跑通 -- SQL 初始化验证必须覆盖: - - `init/pg` 下脚本可按顺序执行 - - `tb_sys_area.sql` 可独立导入 - - `auth`、`upms` 模块脚本可独立维护且组合执行无冲突 -- 区域与租户模型最小验证必须覆盖: - - 区域树可查询 - - 学校租户树可查询 - - 部门树可挂接到学校租户下 -- 带不同 `adcode` 的数据写入与查询能走统一路由键封装 - -## Assumptions - -- 不使用 `Dubbo` -- 首版不做真实业务页面、不做学生端真实业务、不做真实 AI 推理 -- `upms` 继续承担首版系统管理中心职责 -- `tb_sys_area.sql` 是必须接入的区域基础数据脚本,且归档在 `init/pg/sys/` -- 部门当前先按“年级、学科等组织维度”建模,不在首版细化更复杂教学组织规则 diff --git a/docs/plan/课程学习-习题-批改ER与对象类数据流图实施计划.md b/docs/plan/课程学习-习题-批改ER与对象类数据流图实施计划.md deleted file mode 100644 index 2b4165f..0000000 --- a/docs/plan/课程学习-习题-批改ER与对象类数据流图实施计划.md +++ /dev/null @@ -1,44 +0,0 @@ -# 课程学习-习题-批改ER与对象类数据流图实施计划 -问题陈述 -基于现有《AI智能学习系统功能清单》与现有架构图,在不纳入 AI 实现细节的前提下,先完成“课程学习、习题、批改”三条主链路的抽象建模,产出可直接指导后续代码与数据库落地的模块化 ER 图与对象类数据流图(每模块一个目录、两个 drawio 文件)。 -## 当前状态(已核实) -* 现有 `docs/architecture/数据流图(多角色).drawio (1-158)` 已定义 P1-P7 过程与 D1-D6 数据存储,但把 AI 处理作为主链路节点之一,需要按新范围裁剪为“无 AI 依赖的批改主链路”。 -* 现有 `docs/architecture/系统架构与代码架构.drawio (1-260)` 已给出分层和目标服务域(如 homework-content、grading-orchestrator 等),但仓库当前可运行模块仍以骨架为主。 -* 后端现有聚合模块为 `common/apis/ai-client/auth/upms/gateway/boot-dev`(`backend/pom.xml (13-21)`),尚未落地课程/习题/批改独立业务模块。 -* 数据库当前已落地的核心表主要是组织权限与认证、AI占位:`init/pg/upms/10_create_upms_tables.sql (1-153)`、`init/pg/auth/10_create_auth_tables.sql (1-56)`、`init/pg/ai/10_create_ai_tables.sql (1-47)`;尚无课程、题库、作业、提交、批改结果等业务实体。 -* `auth`/`upms` 服务当前仍以示例返回为主(`backend/auth/src/main/java/com/k12study/auth/service/AuthService.java (1-66)`、`backend/upms/src/main/java/com/k12study/upms/service/UpmsQueryService.java (1-111)`),说明本次需要先补齐领域建模图,再推进代码实现。 -## 目标范围与输出目录 -* 范围仅覆盖:课程学习、习题(题库/作业/提交)、批改(规则批改+教师复核+结果反馈)。 -* 明确不纳入:AI 子系统实现细节(OCR/LLM/ASR/模型编排)。 -* 统一输出根目录:`docs/architecture/modules/` -* 每模块目录固定包含:`er图.drawio`、`数据流图.drawio` -* 模块目录规划: - * `docs/architecture/modules/00-组织与权限上下文/` - * `docs/architecture/modules/01-课程学习/` - * `docs/architecture/modules/02-习题与作业/` - * `docs/architecture/modules/03-批改与反馈/` -## 方案设计(ER + 对象类数据流图) -* 00-组织与权限上下文(复用现有 upms/auth) - * ER 聚焦租户、部门、用户、角色、权限、登录态对业务域的外键约束与数据隔离键(`adcode/tenant_id/tenant_path/dept_id/dept_path`)。 - * 数据流图仅保留“身份鉴权、租户路由、权限校验”对象类交互,作为其它模块前置边界。 -* 01-课程学习 - * ER 规划课程主数据与学习行为:课程、章节、课时、知识点、课程资源、标签、课程-标签关联、学习会话、学习进度。 - * 数据流图采用“学生/教师/机构管理员 -> Boundary(前端/API) -> Control(课程编排/进度服务) -> Entity(课程与进度聚合)”表达。 -* 02-习题与作业 - * ER 规划题库与作业域:题目、题目版本、题目-知识点关联、试卷、试卷题目、作业、作业发布对象、作业提交、题目作答明细。 - * 数据流图区分教师发布链路与学生作答链路,明确命令流(发布/提交)与查询流(拉题/看作业)。 -* 03-批改与反馈 - * ER 规划批改闭环:批改任务、批改规则、客观题判分、主观题复核、评分明细、错因标签、错题沉淀、复习任务。 - * 数据流图体现“自动规则判分 + 教师复核”双通道,不依赖 AI;预留抽象接口节点(GradingEnginePort)用于未来替换实现。 -## 采用的设计方法与原因 -* DDD 限界上下文:将课程、习题作业、批改反馈分域,避免单一大模型导致职责耦合与演进困难。 -* 聚合根与不变量建模:以“作业、提交、批改任务”为聚合根,保证状态转换与评分一致性在事务边界内成立。 -* 状态机建模:对作业(草稿/已发布/已截止)与批改任务(待处理/处理中/待复核/已完成)建状态流,减少流程分支歧义。 -* CQRS(读写分离)思路:发布、提交、批改等写操作与学情统计、作业列表等读模型分离,适配高并发查询场景。 -* 事件驱动与Outbox预留:提交后触发批改、批改后触发错题沉淀/复习任务,降低模块同步耦合并提高可扩展性。 -* 多租户路由键统一:所有核心业务实体强制带租户/组织路径字段,保持与现有 upms 路由策略一致。 -## 实施阶段与验收标准 -* 第一阶段:建立统一 drawio 模板(图例、命名规范、主键/外键/基数标注规范、同步/异步箭头规范)。 -* 第二阶段:完成 4 个模块目录与 8 个 drawio 文件,先 ER 后对象类数据流图,确保跨模块实体引用一致。 -* 第三阶段:做跨图一致性校验(实体命名、状态枚举、事件名、租户字段、API对象名),并补充“代码落地映射”注释(对应未来 backend 模块与 init/pg 子目录)。 -* 验收标准:每个模块必须同时具备 ER 与对象类数据流图;ER 图必须标明主外键、基数、关键索引与租户字段;数据流图必须标明 Actor/Boundary/Control/Entity 与关键输入输出对象。 diff --git a/init/pg/01_create_schema.sql b/init/pg/01_create_schema.sql index d346aab..99b5c3a 100644 --- a/init/pg/01_create_schema.sql +++ b/init/pg/01_create_schema.sql @@ -4,4 +4,3 @@ CREATE SCHEMA IF NOT EXISTS ai; CREATE SCHEMA IF NOT EXISTS course; CREATE SCHEMA IF NOT EXISTS question; CREATE SCHEMA IF NOT EXISTS achievement; -CREATE SCHEMA IF NOT EXISTS recommendation; diff --git a/init/pg/02_init_modules.sql b/init/pg/02_init_modules.sql index bb6f136..d210d47 100644 --- a/init/pg/02_init_modules.sql +++ b/init/pg/02_init_modules.sql @@ -4,7 +4,6 @@ \i /docker-entrypoint-initdb.d/course/10_create_course_tables.sql \i /docker-entrypoint-initdb.d/question/10_create_question_tables.sql \i /docker-entrypoint-initdb.d/achievement/10_create_achievement_tables.sql -\i /docker-entrypoint-initdb.d/recommendation/10_create_recommendation_tables.sql \i /docker-entrypoint-initdb.d/sys/sys_area.sql \i /docker-entrypoint-initdb.d/upms/20_init_upms_seed.sql \i /docker-entrypoint-initdb.d/auth/20_init_auth_seed.sql diff --git a/init/pg/question/10_create_question_tables.sql b/init/pg/question/10_create_question_tables.sql index 39f36af..8e8fd25 100644 --- a/init/pg/question/10_create_question_tables.sql +++ b/init/pg/question/10_create_question_tables.sql @@ -563,6 +563,13 @@ CREATE TABLE IF NOT EXISTS question.gd_review_plan ( plan_status VARCHAR(32) NOT NULL DEFAULT 'TODO', tenant_id VARCHAR(64) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT chk_gd_review_plan_stage + CHECK (plan_stage IN ('E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9', 'E10', 'CUSTOM')), + CONSTRAINT chk_gd_review_plan_status + CHECK (plan_status IN ('TODO', 'DONE', 'SKIPPED', 'EXPIRED')), + CONSTRAINT uq_gd_review_plan_wrong_stage + UNIQUE (wrong_question_id, plan_stage), CONSTRAINT fk_gd_review_plan_wrong_question FOREIGN KEY (wrong_question_id) REFERENCES question.gd_wrong_question(wrong_question_id) ); @@ -570,10 +577,99 @@ COMMENT ON TABLE question.gd_review_plan IS '复习计划表'; COMMENT ON COLUMN question.gd_review_plan.review_plan_id IS '复习计划ID'; COMMENT ON COLUMN question.gd_review_plan.wrong_question_id IS '错题记录ID'; COMMENT ON COLUMN question.gd_review_plan.plan_date IS '计划日期'; -COMMENT ON COLUMN question.gd_review_plan.plan_stage IS '阶段(E1/E2/E3)'; +COMMENT ON COLUMN question.gd_review_plan.plan_stage IS '阶段(E1-E10/CUSTOM)'; COMMENT ON COLUMN question.gd_review_plan.plan_status IS '计划状态'; COMMENT ON COLUMN question.gd_review_plan.tenant_id IS '租户ID'; COMMENT ON COLUMN question.gd_review_plan.created_at IS '创建时间'; +COMMENT ON COLUMN question.gd_review_plan.updated_at IS '更新时间'; + +DROP TABLE IF EXISTS question.gd_review_execution CASCADE; +DROP TABLE IF EXISTS question.gd_review_stage_policy CASCADE; +CREATE TABLE IF NOT EXISTS question.gd_review_stage_policy ( + policy_id VARCHAR(64) PRIMARY KEY, + stage_code VARCHAR(16) NOT NULL, + stage_order INTEGER NOT NULL, + interval_days INTEGER NOT NULL, + retry_interval_days INTEGER NOT NULL DEFAULT 1, + pass_threshold NUMERIC(5,2) NOT NULL DEFAULT 60.00, + max_retry_count INTEGER NOT NULL DEFAULT 3, + enabled BOOLEAN NOT NULL DEFAULT TRUE, + tenant_id VARCHAR(64) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT chk_gd_review_stage_policy_stage + CHECK (stage_code IN ('E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9', 'E10')), + CONSTRAINT chk_gd_review_stage_policy_stage_order + CHECK (stage_order > 0), + CONSTRAINT chk_gd_review_stage_policy_interval + CHECK (interval_days >= 0), + CONSTRAINT chk_gd_review_stage_policy_retry_interval + CHECK (retry_interval_days >= 0), + CONSTRAINT chk_gd_review_stage_policy_pass_threshold + CHECK (pass_threshold >= 0 AND pass_threshold <= 100), + CONSTRAINT chk_gd_review_stage_policy_max_retry + CHECK (max_retry_count >= 0), + CONSTRAINT uq_gd_review_stage_policy_tenant_stage + UNIQUE (tenant_id, stage_code), + CONSTRAINT uq_gd_review_stage_policy_tenant_order + UNIQUE (tenant_id, stage_order) +); +COMMENT ON TABLE question.gd_review_stage_policy IS '艾宾浩斯复习阶段策略表'; +COMMENT ON COLUMN question.gd_review_stage_policy.policy_id IS '策略ID'; +COMMENT ON COLUMN question.gd_review_stage_policy.stage_code IS '阶段编码(E1-E10)'; +COMMENT ON COLUMN question.gd_review_stage_policy.stage_order IS '阶段顺序'; +COMMENT ON COLUMN question.gd_review_stage_policy.interval_days IS '通过后下阶段间隔天数'; +COMMENT ON COLUMN question.gd_review_stage_policy.retry_interval_days IS '失败后重试间隔天数'; +COMMENT ON COLUMN question.gd_review_stage_policy.pass_threshold IS '通过阈值(0-100)'; +COMMENT ON COLUMN question.gd_review_stage_policy.max_retry_count IS '当前阶段最大重试次数'; +COMMENT ON COLUMN question.gd_review_stage_policy.enabled IS '是否启用'; +COMMENT ON COLUMN question.gd_review_stage_policy.tenant_id IS '租户ID'; +COMMENT ON COLUMN question.gd_review_stage_policy.created_at IS '创建时间'; +COMMENT ON COLUMN question.gd_review_stage_policy.updated_at IS '更新时间'; + +CREATE TABLE IF NOT EXISTS question.gd_review_execution ( + execution_id VARCHAR(64) PRIMARY KEY, + review_plan_id VARCHAR(64) NOT NULL, + wrong_question_id VARCHAR(64) NOT NULL, + student_id VARCHAR(64) NOT NULL, + stage_code VARCHAR(16) NOT NULL, + result_status VARCHAR(16) NOT NULL, + score NUMERIC(8,2), + reviewed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + next_plan_date DATE, + retry_count INTEGER NOT NULL DEFAULT 0, + evidence_json JSONB NOT NULL DEFAULT '{}'::JSONB, + tenant_id VARCHAR(64) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT chk_gd_review_execution_stage + CHECK (stage_code IN ('E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9', 'E10', 'CUSTOM')), + CONSTRAINT chk_gd_review_execution_result + CHECK (result_status IN ('PASS', 'FAIL', 'SKIPPED')), + CONSTRAINT chk_gd_review_execution_retry + CHECK (retry_count >= 0), + CONSTRAINT chk_gd_review_execution_evidence_json + CHECK (jsonb_typeof(evidence_json) = 'object'), + CONSTRAINT fk_gd_review_execution_plan + FOREIGN KEY (review_plan_id) REFERENCES question.gd_review_plan(review_plan_id) ON DELETE CASCADE, + CONSTRAINT fk_gd_review_execution_wrong_question + FOREIGN KEY (wrong_question_id) REFERENCES question.gd_wrong_question(wrong_question_id), + CONSTRAINT fk_gd_review_execution_student + FOREIGN KEY (student_id) REFERENCES upms.tb_sys_user(user_id) +); +COMMENT ON TABLE question.gd_review_execution IS '复习执行记录表'; +COMMENT ON COLUMN question.gd_review_execution.execution_id IS '执行记录ID'; +COMMENT ON COLUMN question.gd_review_execution.review_plan_id IS '复习计划ID'; +COMMENT ON COLUMN question.gd_review_execution.wrong_question_id IS '错题ID'; +COMMENT ON COLUMN question.gd_review_execution.student_id IS '学员ID'; +COMMENT ON COLUMN question.gd_review_execution.stage_code IS '复习阶段编码'; +COMMENT ON COLUMN question.gd_review_execution.result_status IS '执行结果(PASS/FAIL/SKIPPED)'; +COMMENT ON COLUMN question.gd_review_execution.score IS '本次得分'; +COMMENT ON COLUMN question.gd_review_execution.reviewed_at IS '执行时间'; +COMMENT ON COLUMN question.gd_review_execution.next_plan_date IS '建议下次复习日期'; +COMMENT ON COLUMN question.gd_review_execution.retry_count IS '当前阶段重试次数'; +COMMENT ON COLUMN question.gd_review_execution.evidence_json IS '执行证据JSON(答案摘要/耗时/反馈等)'; +COMMENT ON COLUMN question.gd_review_execution.tenant_id IS '租户ID'; +COMMENT ON COLUMN question.gd_review_execution.created_at IS '创建时间'; DROP TABLE IF EXISTS question.gd_teacher_comment CASCADE; CREATE TABLE IF NOT EXISTS question.gd_teacher_comment ( @@ -604,6 +700,7 @@ DROP TABLE IF EXISTS question.gd_explanation_submission CASCADE; CREATE TABLE IF NOT EXISTS question.gd_explanation_submission ( explanation_id VARCHAR(64) PRIMARY KEY, student_id VARCHAR(64) NOT NULL, + question_id VARCHAR(64) NOT NULL, wrong_question_id VARCHAR(64), source_submission_id VARCHAR(64), source_answer_id VARCHAR(64), @@ -618,6 +715,8 @@ CREATE TABLE IF NOT EXISTS question.gd_explanation_submission ( CHECK (submission_status IN ('SUBMITTED', 'EVALUATED', 'REJECTED')), CONSTRAINT fk_gd_explanation_submission_student FOREIGN KEY (student_id) REFERENCES upms.tb_sys_user(user_id), + CONSTRAINT fk_gd_explanation_submission_question + FOREIGN KEY (question_id) REFERENCES question.hw_question_item(question_id), CONSTRAINT fk_gd_explanation_submission_wrong_question FOREIGN KEY (wrong_question_id) REFERENCES question.gd_wrong_question(wrong_question_id), CONSTRAINT fk_gd_explanation_submission_source_submission @@ -630,6 +729,7 @@ CREATE TABLE IF NOT EXISTS question.gd_explanation_submission ( COMMENT ON TABLE question.gd_explanation_submission IS '费曼讲解提交表'; COMMENT ON COLUMN question.gd_explanation_submission.explanation_id IS '讲解提交ID'; COMMENT ON COLUMN question.gd_explanation_submission.student_id IS '学员ID'; +COMMENT ON COLUMN question.gd_explanation_submission.question_id IS '绑定题目ID(必填)'; COMMENT ON COLUMN question.gd_explanation_submission.wrong_question_id IS '关联错题ID'; COMMENT ON COLUMN question.gd_explanation_submission.source_submission_id IS '来源作业提交ID'; COMMENT ON COLUMN question.gd_explanation_submission.source_answer_id IS '来源答案ID'; @@ -717,13 +817,456 @@ CREATE INDEX IF NOT EXISTS idx_gd_wrong_question_student_status ON question.gd_wrong_question(student_id, mastery_status); CREATE INDEX IF NOT EXISTS idx_gd_review_plan_wrong_question_date ON question.gd_review_plan(wrong_question_id, plan_date); +CREATE INDEX IF NOT EXISTS idx_gd_review_plan_status_date + ON question.gd_review_plan(plan_status, plan_date); +CREATE INDEX IF NOT EXISTS idx_gd_review_plan_stage_status + ON question.gd_review_plan(plan_stage, plan_status, plan_date); +CREATE INDEX IF NOT EXISTS idx_gd_review_stage_policy_tenant_enabled + ON question.gd_review_stage_policy(tenant_id, enabled, stage_order); +CREATE INDEX IF NOT EXISTS idx_gd_review_execution_plan_time + ON question.gd_review_execution(review_plan_id, reviewed_at DESC); +CREATE INDEX IF NOT EXISTS idx_gd_review_execution_wrong_question + ON question.gd_review_execution(wrong_question_id, reviewed_at DESC); +CREATE INDEX IF NOT EXISTS idx_gd_review_execution_student_time + ON question.gd_review_execution(student_id, reviewed_at DESC); CREATE INDEX IF NOT EXISTS idx_gd_teacher_comment_answer_grade ON question.gd_teacher_comment(answer_grade_id); CREATE INDEX IF NOT EXISTS idx_gd_explanation_submission_student ON question.gd_explanation_submission(student_id, created_at DESC); +CREATE INDEX IF NOT EXISTS idx_gd_explanation_submission_question + ON question.gd_explanation_submission(question_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_gd_explanation_submission_wrong_question ON question.gd_explanation_submission(wrong_question_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_gd_explanation_assessment_pass_status ON question.gd_explanation_assessment(pass_status, created_at DESC); CREATE INDEX IF NOT EXISTS idx_gd_explanation_dimension_score_assessment ON question.gd_explanation_dimension_score(assessment_id, dimension_code); + +-- recommendation模块并入question习题模块(rc_*) +DROP TABLE IF EXISTS question.rc_recommendation_task CASCADE; +CREATE TABLE IF NOT EXISTS question.rc_recommendation_task ( + task_id VARCHAR(64) PRIMARY KEY, + user_id VARCHAR(64) NOT NULL, + scene_code VARCHAR(32) NOT NULL, + trigger_source VARCHAR(32) NOT NULL DEFAULT 'SCHEDULE', + strategy_version VARCHAR(32), + profile_snapshot_json JSONB NOT NULL DEFAULT '{}'::JSONB, + status VARCHAR(16) NOT NULL DEFAULT 'CREATED', + tenant_id VARCHAR(64) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT chk_rc_recommendation_task_status + CHECK (status IN ('CREATED', 'RUNNING', 'DONE', 'FAILED')), + CONSTRAINT chk_rc_recommendation_task_profile_json + CHECK (jsonb_typeof(profile_snapshot_json) = 'object'), + CONSTRAINT fk_rc_recommendation_task_user + FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id) +); +COMMENT ON TABLE question.rc_recommendation_task IS '推荐任务表'; +COMMENT ON COLUMN question.rc_recommendation_task.task_id IS '推荐任务ID'; +COMMENT ON COLUMN question.rc_recommendation_task.user_id IS '推荐目标用户ID'; +COMMENT ON COLUMN question.rc_recommendation_task.scene_code IS '推荐场景编码(HOME/WRONGBOOK/REVIEW等)'; +COMMENT ON COLUMN question.rc_recommendation_task.trigger_source IS '触发来源(SCHEDULE/EVENT/MANUAL)'; +COMMENT ON COLUMN question.rc_recommendation_task.strategy_version IS '推荐策略版本'; +COMMENT ON COLUMN question.rc_recommendation_task.profile_snapshot_json IS '画像快照JSON'; +COMMENT ON COLUMN question.rc_recommendation_task.status IS '任务状态'; +COMMENT ON COLUMN question.rc_recommendation_task.tenant_id IS '租户ID'; +COMMENT ON COLUMN question.rc_recommendation_task.created_at IS '创建时间'; +COMMENT ON COLUMN question.rc_recommendation_task.updated_at IS '更新时间'; + +DROP TABLE IF EXISTS question.rc_recommendation_item CASCADE; +CREATE TABLE IF NOT EXISTS question.rc_recommendation_item ( + item_id VARCHAR(64) PRIMARY KEY, + task_id VARCHAR(64) NOT NULL, + content_type VARCHAR(32) NOT NULL, + content_object_id VARCHAR(64) NOT NULL, + content_object_type VARCHAR(64) NOT NULL, + rank_no INTEGER NOT NULL, + score NUMERIC(10,6), + reason_codes VARCHAR(255), + metadata_json JSONB NOT NULL DEFAULT '{}'::JSONB, + tenant_id VARCHAR(64) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT chk_rc_recommendation_item_content_type + CHECK (content_type IN ('COURSE', 'QUESTION', 'PAPER', 'WRONG_QUESTION', 'REVIEW_PLAN', 'MIXED')), + CONSTRAINT chk_rc_recommendation_item_metadata_json + CHECK (jsonb_typeof(metadata_json) = 'object'), + CONSTRAINT uq_rc_recommendation_item_task_rank + UNIQUE (task_id, rank_no), + CONSTRAINT fk_rc_recommendation_item_task + FOREIGN KEY (task_id) REFERENCES question.rc_recommendation_task(task_id) ON DELETE CASCADE +); +COMMENT ON TABLE question.rc_recommendation_item IS '推荐结果明细表'; +COMMENT ON COLUMN question.rc_recommendation_item.item_id IS '推荐项ID'; +COMMENT ON COLUMN question.rc_recommendation_item.task_id IS '推荐任务ID'; +COMMENT ON COLUMN question.rc_recommendation_item.content_type IS '内容类型'; +COMMENT ON COLUMN question.rc_recommendation_item.content_object_id IS '内容对象ID'; +COMMENT ON COLUMN question.rc_recommendation_item.content_object_type IS '内容对象类型'; +COMMENT ON COLUMN question.rc_recommendation_item.rank_no IS '推荐位次'; +COMMENT ON COLUMN question.rc_recommendation_item.score IS '推荐分'; +COMMENT ON COLUMN question.rc_recommendation_item.reason_codes IS '推荐原因编码集合'; +COMMENT ON COLUMN question.rc_recommendation_item.metadata_json IS '扩展元数据JSON'; +COMMENT ON COLUMN question.rc_recommendation_item.tenant_id IS '租户ID'; +COMMENT ON COLUMN question.rc_recommendation_item.created_at IS '创建时间'; + +DROP TABLE IF EXISTS question.rc_recommendation_feedback CASCADE; +CREATE TABLE IF NOT EXISTS question.rc_recommendation_feedback ( + feedback_id VARCHAR(64) PRIMARY KEY, + item_id VARCHAR(64) NOT NULL, + user_id VARCHAR(64) NOT NULL, + event_type VARCHAR(16) NOT NULL, + event_value NUMERIC(10,4), + event_detail_json JSONB NOT NULL DEFAULT '{}'::JSONB, + event_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + tenant_id VARCHAR(64) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT chk_rc_recommendation_feedback_event_type + CHECK (event_type IN ('EXPOSE', 'CLICK', 'START', 'COMPLETE', 'DISLIKE', 'SKIP')), + CONSTRAINT chk_rc_recommendation_feedback_detail_json + CHECK (jsonb_typeof(event_detail_json) = 'object'), + CONSTRAINT fk_rc_recommendation_feedback_item + FOREIGN KEY (item_id) REFERENCES question.rc_recommendation_item(item_id) ON DELETE CASCADE, + CONSTRAINT fk_rc_recommendation_feedback_user + FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id) +); +COMMENT ON TABLE question.rc_recommendation_feedback IS '推荐反馈事件表'; +COMMENT ON COLUMN question.rc_recommendation_feedback.feedback_id IS '反馈事件ID'; +COMMENT ON COLUMN question.rc_recommendation_feedback.item_id IS '推荐项ID'; +COMMENT ON COLUMN question.rc_recommendation_feedback.user_id IS '用户ID'; +COMMENT ON COLUMN question.rc_recommendation_feedback.event_type IS '事件类型'; +COMMENT ON COLUMN question.rc_recommendation_feedback.event_value IS '事件值(可选)'; +COMMENT ON COLUMN question.rc_recommendation_feedback.event_detail_json IS '事件详情JSON'; +COMMENT ON COLUMN question.rc_recommendation_feedback.event_time IS '事件发生时间'; +COMMENT ON COLUMN question.rc_recommendation_feedback.tenant_id IS '租户ID'; +COMMENT ON COLUMN question.rc_recommendation_feedback.created_at IS '创建时间'; + +DROP TABLE IF EXISTS question.rc_recommendation_effect_daily CASCADE; +CREATE TABLE IF NOT EXISTS question.rc_recommendation_effect_daily ( + stat_date DATE NOT NULL, + scene_code VARCHAR(32) NOT NULL, + strategy_version VARCHAR(32) NOT NULL, + tenant_id VARCHAR(64) NOT NULL, + expose_count BIGINT NOT NULL DEFAULT 0, + click_count BIGINT NOT NULL DEFAULT 0, + complete_count BIGINT NOT NULL DEFAULT 0, + dislike_count BIGINT NOT NULL DEFAULT 0, + ctr NUMERIC(10,6) NOT NULL DEFAULT 0, + complete_rate NUMERIC(10,6) NOT NULL DEFAULT 0, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (stat_date, scene_code, strategy_version, tenant_id) +); +COMMENT ON TABLE question.rc_recommendation_effect_daily IS '推荐效果日统计表'; +COMMENT ON COLUMN question.rc_recommendation_effect_daily.stat_date IS '统计日期'; +COMMENT ON COLUMN question.rc_recommendation_effect_daily.scene_code IS '推荐场景编码'; +COMMENT ON COLUMN question.rc_recommendation_effect_daily.strategy_version IS '策略版本'; +COMMENT ON COLUMN question.rc_recommendation_effect_daily.tenant_id IS '租户ID'; +COMMENT ON COLUMN question.rc_recommendation_effect_daily.expose_count IS '曝光次数'; +COMMENT ON COLUMN question.rc_recommendation_effect_daily.click_count IS '点击次数'; +COMMENT ON COLUMN question.rc_recommendation_effect_daily.complete_count IS '完成次数'; +COMMENT ON COLUMN question.rc_recommendation_effect_daily.dislike_count IS '不喜欢次数'; +COMMENT ON COLUMN question.rc_recommendation_effect_daily.ctr IS '点击率'; +COMMENT ON COLUMN question.rc_recommendation_effect_daily.complete_rate IS '完成率'; +COMMENT ON COLUMN question.rc_recommendation_effect_daily.created_at IS '创建时间'; +COMMENT ON COLUMN question.rc_recommendation_effect_daily.updated_at IS '更新时间'; + +CREATE INDEX IF NOT EXISTS idx_rc_recommendation_task_user_scene + ON question.rc_recommendation_task(user_id, scene_code, created_at DESC); +CREATE INDEX IF NOT EXISTS idx_rc_recommendation_task_tenant_status + ON question.rc_recommendation_task(tenant_id, status, created_at DESC); +CREATE INDEX IF NOT EXISTS idx_rc_recommendation_item_task + ON question.rc_recommendation_item(task_id, rank_no); +CREATE INDEX IF NOT EXISTS idx_rc_recommendation_item_content + ON question.rc_recommendation_item(content_object_type, content_object_id); +CREATE INDEX IF NOT EXISTS idx_rc_recommendation_feedback_item_event + ON question.rc_recommendation_feedback(item_id, event_type, event_time DESC); +CREATE INDEX IF NOT EXISTS idx_rc_recommendation_feedback_user_event + ON question.rc_recommendation_feedback(user_id, event_type, event_time DESC); +CREATE INDEX IF NOT EXISTS idx_rc_recommendation_effect_daily_tenant_date + ON question.rc_recommendation_effect_daily(tenant_id, stat_date DESC); + +DROP TABLE IF EXISTS question.rc_student_profile_feature CASCADE; +DROP TABLE IF EXISTS question.rc_student_profile_snapshot CASCADE; +CREATE TABLE IF NOT EXISTS question.rc_student_profile_snapshot ( + profile_id VARCHAR(64) PRIMARY KEY, + user_id VARCHAR(64) NOT NULL, + profile_version VARCHAR(32) NOT NULL, + profile_date DATE NOT NULL, + feature_source_version VARCHAR(64), + profile_json JSONB NOT NULL DEFAULT '{}'::JSONB, + confidence_score NUMERIC(6,4), + profile_status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE', + tenant_id VARCHAR(64) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT uq_rc_student_profile_snapshot_user_version + UNIQUE (user_id, profile_version), + CONSTRAINT chk_rc_student_profile_snapshot_json + CHECK (jsonb_typeof(profile_json) = 'object'), + CONSTRAINT chk_rc_student_profile_snapshot_status + CHECK (profile_status IN ('ACTIVE', 'ARCHIVED')), + CONSTRAINT fk_rc_student_profile_snapshot_user + FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id) +); +COMMENT ON TABLE question.rc_student_profile_snapshot IS '学员画像快照表'; +COMMENT ON COLUMN question.rc_student_profile_snapshot.profile_id IS '画像ID'; +COMMENT ON COLUMN question.rc_student_profile_snapshot.user_id IS '学员ID'; +COMMENT ON COLUMN question.rc_student_profile_snapshot.profile_version IS '画像版本'; +COMMENT ON COLUMN question.rc_student_profile_snapshot.profile_date IS '画像日期'; +COMMENT ON COLUMN question.rc_student_profile_snapshot.feature_source_version IS '特征来源版本'; +COMMENT ON COLUMN question.rc_student_profile_snapshot.profile_json IS '画像JSON'; +COMMENT ON COLUMN question.rc_student_profile_snapshot.confidence_score IS '画像置信度'; +COMMENT ON COLUMN question.rc_student_profile_snapshot.profile_status IS '画像状态'; +COMMENT ON COLUMN question.rc_student_profile_snapshot.tenant_id IS '租户ID'; +COMMENT ON COLUMN question.rc_student_profile_snapshot.created_at IS '创建时间'; +COMMENT ON COLUMN question.rc_student_profile_snapshot.updated_at IS '更新时间'; + +CREATE TABLE IF NOT EXISTS question.rc_student_profile_feature ( + feature_id VARCHAR(64) PRIMARY KEY, + profile_id VARCHAR(64) NOT NULL, + feature_group VARCHAR(32) NOT NULL, + feature_key VARCHAR(64) NOT NULL, + feature_value_num NUMERIC(18,6), + feature_value_text VARCHAR(512), + feature_value_json JSONB, + value_type VARCHAR(16) NOT NULL DEFAULT 'NUM', + source_domain VARCHAR(32) NOT NULL, + source_ref_id VARCHAR(64), + sample_time TIMESTAMP, + tenant_id VARCHAR(64) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT chk_rc_student_profile_feature_value_type + CHECK (value_type IN ('NUM', 'TEXT', 'JSON', 'BOOL')), + CONSTRAINT chk_rc_student_profile_feature_source_domain + CHECK (source_domain IN ('COURSE', 'HOMEWORK', 'GRADING', 'REVIEW', 'RECOMMENDATION', 'ACHIEVEMENT', 'SYSTEM')), + CONSTRAINT chk_rc_student_profile_feature_json + CHECK (feature_value_json IS NULL OR jsonb_typeof(feature_value_json) IN ('object', 'array', 'string', 'number', 'boolean')), + CONSTRAINT fk_rc_student_profile_feature_profile + FOREIGN KEY (profile_id) REFERENCES question.rc_student_profile_snapshot(profile_id) ON DELETE CASCADE +); +COMMENT ON TABLE question.rc_student_profile_feature IS '学员画像特征明细表'; +COMMENT ON COLUMN question.rc_student_profile_feature.feature_id IS '特征ID'; +COMMENT ON COLUMN question.rc_student_profile_feature.profile_id IS '画像ID'; +COMMENT ON COLUMN question.rc_student_profile_feature.feature_group IS '特征组'; +COMMENT ON COLUMN question.rc_student_profile_feature.feature_key IS '特征键'; +COMMENT ON COLUMN question.rc_student_profile_feature.feature_value_num IS '数值特征值'; +COMMENT ON COLUMN question.rc_student_profile_feature.feature_value_text IS '文本特征值'; +COMMENT ON COLUMN question.rc_student_profile_feature.feature_value_json IS 'JSON特征值'; +COMMENT ON COLUMN question.rc_student_profile_feature.value_type IS '值类型'; +COMMENT ON COLUMN question.rc_student_profile_feature.source_domain IS '来源域'; +COMMENT ON COLUMN question.rc_student_profile_feature.source_ref_id IS '来源对象ID'; +COMMENT ON COLUMN question.rc_student_profile_feature.sample_time IS '采样时间'; +COMMENT ON COLUMN question.rc_student_profile_feature.tenant_id IS '租户ID'; +COMMENT ON COLUMN question.rc_student_profile_feature.created_at IS '创建时间'; + +DROP TABLE IF EXISTS question.rc_learning_loop_event CASCADE; +DROP TABLE IF EXISTS question.rc_learning_loop_case CASCADE; +CREATE TABLE IF NOT EXISTS question.rc_learning_loop_case ( + loop_case_id VARCHAR(64) PRIMARY KEY, + user_id VARCHAR(64) NOT NULL, + wrong_question_id VARCHAR(64) NOT NULL, + review_plan_id VARCHAR(64), + profile_id VARCHAR(64), + recommendation_task_id VARCHAR(64), + recommendation_item_id VARCHAR(64), + feedback_id VARCHAR(64), + user_achievement_id VARCHAR(64), + loop_status VARCHAR(16) NOT NULL DEFAULT 'OPEN', + effect_score NUMERIC(10,4), + opened_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + closed_at TIMESTAMP, + tenant_id VARCHAR(64) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT chk_rc_learning_loop_case_status + CHECK (loop_status IN ('OPEN', 'IN_PROGRESS', 'CLOSED', 'FAILED')), + CONSTRAINT chk_rc_learning_loop_case_closed_at + CHECK (closed_at IS NULL OR closed_at >= opened_at), + CONSTRAINT fk_rc_learning_loop_case_user + FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id), + CONSTRAINT fk_rc_learning_loop_case_wrong_question + FOREIGN KEY (wrong_question_id) REFERENCES question.gd_wrong_question(wrong_question_id), + CONSTRAINT fk_rc_learning_loop_case_review_plan + FOREIGN KEY (review_plan_id) REFERENCES question.gd_review_plan(review_plan_id), + CONSTRAINT fk_rc_learning_loop_case_profile + FOREIGN KEY (profile_id) REFERENCES question.rc_student_profile_snapshot(profile_id), + CONSTRAINT fk_rc_learning_loop_case_recommendation_task + FOREIGN KEY (recommendation_task_id) REFERENCES question.rc_recommendation_task(task_id), + CONSTRAINT fk_rc_learning_loop_case_recommendation_item + FOREIGN KEY (recommendation_item_id) REFERENCES question.rc_recommendation_item(item_id), + CONSTRAINT fk_rc_learning_loop_case_feedback + FOREIGN KEY (feedback_id) REFERENCES question.rc_recommendation_feedback(feedback_id) +); +COMMENT ON TABLE question.rc_learning_loop_case IS '学习闭环主表(错题-复习-推荐-成就)'; +COMMENT ON COLUMN question.rc_learning_loop_case.loop_case_id IS '闭环案例ID'; +COMMENT ON COLUMN question.rc_learning_loop_case.user_id IS '用户ID'; +COMMENT ON COLUMN question.rc_learning_loop_case.wrong_question_id IS '错题ID'; +COMMENT ON COLUMN question.rc_learning_loop_case.review_plan_id IS '复习计划ID'; +COMMENT ON COLUMN question.rc_learning_loop_case.profile_id IS '画像ID'; +COMMENT ON COLUMN question.rc_learning_loop_case.recommendation_task_id IS '推荐任务ID'; +COMMENT ON COLUMN question.rc_learning_loop_case.recommendation_item_id IS '推荐项ID'; +COMMENT ON COLUMN question.rc_learning_loop_case.feedback_id IS '反馈事件ID'; +COMMENT ON COLUMN question.rc_learning_loop_case.user_achievement_id IS '成就记录ID'; +COMMENT ON COLUMN question.rc_learning_loop_case.loop_status IS '闭环状态'; +COMMENT ON COLUMN question.rc_learning_loop_case.effect_score IS '闭环效果分'; +COMMENT ON COLUMN question.rc_learning_loop_case.opened_at IS '闭环开启时间'; +COMMENT ON COLUMN question.rc_learning_loop_case.closed_at IS '闭环关闭时间'; +COMMENT ON COLUMN question.rc_learning_loop_case.tenant_id IS '租户ID'; +COMMENT ON COLUMN question.rc_learning_loop_case.created_at IS '创建时间'; +COMMENT ON COLUMN question.rc_learning_loop_case.updated_at IS '更新时间'; + +CREATE TABLE IF NOT EXISTS question.rc_learning_loop_event ( + loop_event_id VARCHAR(64) PRIMARY KEY, + loop_case_id VARCHAR(64) NOT NULL, + stage_code VARCHAR(32) NOT NULL, + stage_status VARCHAR(16) NOT NULL DEFAULT 'DONE', + event_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + event_score NUMERIC(10,4), + event_detail_json JSONB NOT NULL DEFAULT '{}'::JSONB, + tenant_id VARCHAR(64) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT chk_rc_learning_loop_event_stage_code + CHECK (stage_code IN ('WRONG_CAPTURED', 'REVIEW_PLANNED', 'PROFILE_UPDATED', 'RECOMMENDED', 'EXPOSED', 'ENGAGED', 'REVIEW_COMPLETED', 'ACHIEVED')), + CONSTRAINT chk_rc_learning_loop_event_stage_status + CHECK (stage_status IN ('PENDING', 'DONE', 'FAILED', 'SKIPPED')), + CONSTRAINT chk_rc_learning_loop_event_detail_json + CHECK (jsonb_typeof(event_detail_json) = 'object'), + CONSTRAINT fk_rc_learning_loop_event_case + FOREIGN KEY (loop_case_id) REFERENCES question.rc_learning_loop_case(loop_case_id) ON DELETE CASCADE +); +COMMENT ON TABLE question.rc_learning_loop_event IS '学习闭环阶段事件表'; +COMMENT ON COLUMN question.rc_learning_loop_event.loop_event_id IS '闭环事件ID'; +COMMENT ON COLUMN question.rc_learning_loop_event.loop_case_id IS '闭环案例ID'; +COMMENT ON COLUMN question.rc_learning_loop_event.stage_code IS '阶段编码'; +COMMENT ON COLUMN question.rc_learning_loop_event.stage_status IS '阶段状态'; +COMMENT ON COLUMN question.rc_learning_loop_event.event_time IS '事件时间'; +COMMENT ON COLUMN question.rc_learning_loop_event.event_score IS '阶段得分'; +COMMENT ON COLUMN question.rc_learning_loop_event.event_detail_json IS '事件详情JSON'; +COMMENT ON COLUMN question.rc_learning_loop_event.tenant_id IS '租户ID'; +COMMENT ON COLUMN question.rc_learning_loop_event.created_at IS '创建时间'; + +DROP TABLE IF EXISTS question.rc_learning_loop_effect_daily CASCADE; +CREATE TABLE IF NOT EXISTS question.rc_learning_loop_effect_daily ( + stat_date DATE NOT NULL, + tenant_id VARCHAR(64) NOT NULL, + opened_case_count BIGINT NOT NULL DEFAULT 0, + closed_case_count BIGINT NOT NULL DEFAULT 0, + achieved_case_count BIGINT NOT NULL DEFAULT 0, + avg_effect_score NUMERIC(10,4) NOT NULL DEFAULT 0, + avg_close_hours NUMERIC(10,4) NOT NULL DEFAULT 0, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (stat_date, tenant_id) +); +COMMENT ON TABLE question.rc_learning_loop_effect_daily IS '学习闭环效果日统计表'; +COMMENT ON COLUMN question.rc_learning_loop_effect_daily.stat_date IS '统计日期'; +COMMENT ON COLUMN question.rc_learning_loop_effect_daily.tenant_id IS '租户ID'; +COMMENT ON COLUMN question.rc_learning_loop_effect_daily.opened_case_count IS '开启案例数'; +COMMENT ON COLUMN question.rc_learning_loop_effect_daily.closed_case_count IS '关闭案例数'; +COMMENT ON COLUMN question.rc_learning_loop_effect_daily.achieved_case_count IS '达成成就案例数'; +COMMENT ON COLUMN question.rc_learning_loop_effect_daily.avg_effect_score IS '平均效果分'; +COMMENT ON COLUMN question.rc_learning_loop_effect_daily.avg_close_hours IS '平均闭环时长(小时)'; +COMMENT ON COLUMN question.rc_learning_loop_effect_daily.created_at IS '创建时间'; +COMMENT ON COLUMN question.rc_learning_loop_effect_daily.updated_at IS '更新时间'; + +CREATE INDEX IF NOT EXISTS idx_rc_student_profile_snapshot_user_date + ON question.rc_student_profile_snapshot(user_id, profile_date DESC); +CREATE INDEX IF NOT EXISTS idx_rc_student_profile_snapshot_tenant_version + ON question.rc_student_profile_snapshot(tenant_id, profile_version, created_at DESC); +CREATE INDEX IF NOT EXISTS idx_rc_student_profile_feature_profile_group + ON question.rc_student_profile_feature(profile_id, feature_group, feature_key); +CREATE INDEX IF NOT EXISTS idx_rc_student_profile_feature_source + ON question.rc_student_profile_feature(source_domain, source_ref_id); +CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_case_user_status + ON question.rc_learning_loop_case(user_id, loop_status, opened_at DESC); +CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_case_wrong_question + ON question.rc_learning_loop_case(wrong_question_id, opened_at DESC); +CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_case_tenant_status + ON question.rc_learning_loop_case(tenant_id, loop_status, opened_at DESC); +CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_event_case_stage_time + ON question.rc_learning_loop_event(loop_case_id, stage_code, event_time DESC); +CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_event_tenant_time + ON question.rc_learning_loop_event(tenant_id, event_time DESC); +CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_effect_daily_tenant_date + ON question.rc_learning_loop_effect_daily(tenant_id, stat_date DESC); + +DROP TABLE IF EXISTS question.rc_loop_metric_map CASCADE; +DROP TABLE IF EXISTS question.rc_loop_stage_achievement_map CASCADE; +CREATE TABLE IF NOT EXISTS question.rc_loop_stage_achievement_map ( + stage_map_id VARCHAR(64) PRIMARY KEY, + stage_code VARCHAR(32) NOT NULL, + event_code VARCHAR(64) NOT NULL, + template_id VARCHAR(64), + metric_id VARCHAR(64), + award_source VARCHAR(32) NOT NULL DEFAULT 'RULE_ENGINE', + priority INTEGER NOT NULL DEFAULT 100, + enabled BOOLEAN NOT NULL DEFAULT TRUE, + ext_json JSONB NOT NULL DEFAULT '{}'::JSONB, + tenant_id VARCHAR(64) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT uq_rc_loop_stage_achievement_map + UNIQUE (tenant_id, stage_code, event_code, priority), + CONSTRAINT chk_rc_loop_stage_achievement_map_stage_code + CHECK (stage_code IN ('WRONG_CAPTURED', 'REVIEW_PLANNED', 'PROFILE_UPDATED', 'RECOMMENDED', 'EXPOSED', 'ENGAGED', 'REVIEW_COMPLETED', 'ACHIEVED')), + CONSTRAINT chk_rc_loop_stage_achievement_map_award_source + CHECK (award_source IN ('RULE_ENGINE', 'MANUAL', 'SYSTEM')), + CONSTRAINT chk_rc_loop_stage_achievement_map_ext_json + CHECK (jsonb_typeof(ext_json) = 'object') +); +COMMENT ON TABLE question.rc_loop_stage_achievement_map IS '闭环阶段到成就事件映射表(通用配置)'; +COMMENT ON COLUMN question.rc_loop_stage_achievement_map.stage_map_id IS '阶段映射ID'; +COMMENT ON COLUMN question.rc_loop_stage_achievement_map.stage_code IS '闭环阶段编码'; +COMMENT ON COLUMN question.rc_loop_stage_achievement_map.event_code IS '成就事件编码'; +COMMENT ON COLUMN question.rc_loop_stage_achievement_map.template_id IS '成就规则模板ID'; +COMMENT ON COLUMN question.rc_loop_stage_achievement_map.metric_id IS '成就指标ID'; +COMMENT ON COLUMN question.rc_loop_stage_achievement_map.award_source IS '发放来源'; +COMMENT ON COLUMN question.rc_loop_stage_achievement_map.priority IS '优先级'; +COMMENT ON COLUMN question.rc_loop_stage_achievement_map.enabled IS '是否启用'; +COMMENT ON COLUMN question.rc_loop_stage_achievement_map.ext_json IS '扩展配置JSON'; +COMMENT ON COLUMN question.rc_loop_stage_achievement_map.tenant_id IS '租户ID'; +COMMENT ON COLUMN question.rc_loop_stage_achievement_map.created_at IS '创建时间'; +COMMENT ON COLUMN question.rc_loop_stage_achievement_map.updated_at IS '更新时间'; + +CREATE TABLE IF NOT EXISTS question.rc_loop_metric_map ( + metric_map_id VARCHAR(64) PRIMARY KEY, + stage_code VARCHAR(32) NOT NULL, + metric_id VARCHAR(64) NOT NULL, + source_field VARCHAR(64) NOT NULL, + agg_method VARCHAR(16) NOT NULL DEFAULT 'LATEST', + weight NUMERIC(6,4) NOT NULL DEFAULT 1.0000, + enabled BOOLEAN NOT NULL DEFAULT TRUE, + ext_json JSONB NOT NULL DEFAULT '{}'::JSONB, + tenant_id VARCHAR(64) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT uq_rc_loop_metric_map + UNIQUE (tenant_id, stage_code, metric_id, source_field), + CONSTRAINT chk_rc_loop_metric_map_stage_code + CHECK (stage_code IN ('WRONG_CAPTURED', 'REVIEW_PLANNED', 'PROFILE_UPDATED', 'RECOMMENDED', 'EXPOSED', 'ENGAGED', 'REVIEW_COMPLETED', 'ACHIEVED')), + CONSTRAINT chk_rc_loop_metric_map_agg_method + CHECK (agg_method IN ('LATEST', 'SUM', 'AVG', 'MAX', 'MIN', 'COUNT')), + CONSTRAINT chk_rc_loop_metric_map_weight + CHECK (weight > 0 AND weight <= 1), + CONSTRAINT chk_rc_loop_metric_map_ext_json + CHECK (jsonb_typeof(ext_json) = 'object') +); +COMMENT ON TABLE question.rc_loop_metric_map IS '闭环指标映射表(通用配置)'; +COMMENT ON COLUMN question.rc_loop_metric_map.metric_map_id IS '指标映射ID'; +COMMENT ON COLUMN question.rc_loop_metric_map.stage_code IS '闭环阶段编码'; +COMMENT ON COLUMN question.rc_loop_metric_map.metric_id IS '成就指标ID'; +COMMENT ON COLUMN question.rc_loop_metric_map.source_field IS '来源字段名'; +COMMENT ON COLUMN question.rc_loop_metric_map.agg_method IS '聚合方法'; +COMMENT ON COLUMN question.rc_loop_metric_map.weight IS '权重'; +COMMENT ON COLUMN question.rc_loop_metric_map.enabled IS '是否启用'; +COMMENT ON COLUMN question.rc_loop_metric_map.ext_json IS '扩展配置JSON'; +COMMENT ON COLUMN question.rc_loop_metric_map.tenant_id IS '租户ID'; +COMMENT ON COLUMN question.rc_loop_metric_map.created_at IS '创建时间'; +COMMENT ON COLUMN question.rc_loop_metric_map.updated_at IS '更新时间'; + +CREATE INDEX IF NOT EXISTS idx_rc_loop_stage_achievement_map_tenant_stage + ON question.rc_loop_stage_achievement_map(tenant_id, stage_code, enabled, priority); +CREATE INDEX IF NOT EXISTS idx_rc_loop_stage_achievement_map_event + ON question.rc_loop_stage_achievement_map(event_code, tenant_id, enabled); +CREATE INDEX IF NOT EXISTS idx_rc_loop_metric_map_tenant_stage + ON question.rc_loop_metric_map(tenant_id, stage_code, enabled); diff --git a/init/pg/question/20_init_question_seed.sql b/init/pg/question/20_init_question_seed.sql index f8e4c4a..86f168c 100644 --- a/init/pg/question/20_init_question_seed.sql +++ b/init/pg/question/20_init_question_seed.sql @@ -3,3 +3,50 @@ -- 批改与反馈模块初始化数据占位 -- 按需补充错因标签、统一答案批改结果、复习计划策略等种子数据 + +-- 艾宾浩斯复习阶段默认策略(E1-E6:1/2/4/7/15/30天) +WITH stage_defaults(stage_code, stage_order, interval_days, retry_interval_days, pass_threshold, max_retry_count) AS ( + VALUES + ('E1', 1, 1, 1, 60.00, 3), + ('E2', 2, 2, 1, 65.00, 3), + ('E3', 3, 4, 1, 70.00, 2), + ('E4', 4, 7, 2, 75.00, 2), + ('E5', 5, 15, 2, 80.00, 2), + ('E6', 6, 30, 3, 85.00, 1) +) +INSERT INTO question.gd_review_stage_policy ( + policy_id, + stage_code, + stage_order, + interval_days, + retry_interval_days, + pass_threshold, + max_retry_count, + enabled, + tenant_id, + created_at, + updated_at +) +SELECT + 'RSP_' || SUBSTRING(MD5(t.tenant_id || ':' || s.stage_code) FROM 1 FOR 24) AS policy_id, + s.stage_code, + s.stage_order, + s.interval_days, + s.retry_interval_days, + s.pass_threshold, + s.max_retry_count, + TRUE, + t.tenant_id, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP +FROM upms.tb_sys_tenant t +CROSS JOIN stage_defaults s +ON CONFLICT (tenant_id, stage_code) +DO UPDATE SET + stage_order = EXCLUDED.stage_order, + interval_days = EXCLUDED.interval_days, + retry_interval_days = EXCLUDED.retry_interval_days, + pass_threshold = EXCLUDED.pass_threshold, + max_retry_count = EXCLUDED.max_retry_count, + enabled = EXCLUDED.enabled, + updated_at = CURRENT_TIMESTAMP; diff --git a/init/pg/recommendation/10_create_recommendation_tables.sql b/init/pg/recommendation/10_create_recommendation_tables.sql deleted file mode 100644 index ea9dbeb..0000000 --- a/init/pg/recommendation/10_create_recommendation_tables.sql +++ /dev/null @@ -1,440 +0,0 @@ -DROP SCHEMA IF EXISTS recommendation CASCADE; -CREATE SCHEMA IF NOT EXISTS recommendation; - -DROP TABLE IF EXISTS recommendation.rc_recommendation_task CASCADE; -CREATE TABLE IF NOT EXISTS recommendation.rc_recommendation_task ( - task_id VARCHAR(64) PRIMARY KEY, - user_id VARCHAR(64) NOT NULL, - scene_code VARCHAR(32) NOT NULL, - trigger_source VARCHAR(32) NOT NULL DEFAULT 'SCHEDULE', - strategy_version VARCHAR(32), - profile_snapshot_json JSONB NOT NULL DEFAULT '{}'::JSONB, - status VARCHAR(16) NOT NULL DEFAULT 'CREATED', - tenant_id VARCHAR(64) NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT chk_rc_recommendation_task_status - CHECK (status IN ('CREATED', 'RUNNING', 'DONE', 'FAILED')), - CONSTRAINT chk_rc_recommendation_task_profile_json - CHECK (jsonb_typeof(profile_snapshot_json) = 'object'), - CONSTRAINT fk_rc_recommendation_task_user - FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id) -); -COMMENT ON TABLE recommendation.rc_recommendation_task IS '推荐任务表'; -COMMENT ON COLUMN recommendation.rc_recommendation_task.task_id IS '推荐任务ID'; -COMMENT ON COLUMN recommendation.rc_recommendation_task.user_id IS '推荐目标用户ID'; -COMMENT ON COLUMN recommendation.rc_recommendation_task.scene_code IS '推荐场景编码(HOME/WRONGBOOK/REVIEW等)'; -COMMENT ON COLUMN recommendation.rc_recommendation_task.trigger_source IS '触发来源(SCHEDULE/EVENT/MANUAL)'; -COMMENT ON COLUMN recommendation.rc_recommendation_task.strategy_version IS '推荐策略版本'; -COMMENT ON COLUMN recommendation.rc_recommendation_task.profile_snapshot_json IS '画像快照JSON'; -COMMENT ON COLUMN recommendation.rc_recommendation_task.status IS '任务状态'; -COMMENT ON COLUMN recommendation.rc_recommendation_task.tenant_id IS '租户ID'; -COMMENT ON COLUMN recommendation.rc_recommendation_task.created_at IS '创建时间'; -COMMENT ON COLUMN recommendation.rc_recommendation_task.updated_at IS '更新时间'; - -DROP TABLE IF EXISTS recommendation.rc_recommendation_item CASCADE; -CREATE TABLE IF NOT EXISTS recommendation.rc_recommendation_item ( - item_id VARCHAR(64) PRIMARY KEY, - task_id VARCHAR(64) NOT NULL, - content_type VARCHAR(32) NOT NULL, - content_object_id VARCHAR(64) NOT NULL, - content_object_type VARCHAR(64) NOT NULL, - rank_no INTEGER NOT NULL, - score NUMERIC(10,6), - reason_codes VARCHAR(255), - metadata_json JSONB NOT NULL DEFAULT '{}'::JSONB, - tenant_id VARCHAR(64) NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT chk_rc_recommendation_item_content_type - CHECK (content_type IN ('COURSE', 'QUESTION', 'PAPER', 'WRONG_QUESTION', 'REVIEW_PLAN', 'MIXED')), - CONSTRAINT chk_rc_recommendation_item_metadata_json - CHECK (jsonb_typeof(metadata_json) = 'object'), - CONSTRAINT uq_rc_recommendation_item_task_rank - UNIQUE (task_id, rank_no), - CONSTRAINT fk_rc_recommendation_item_task - FOREIGN KEY (task_id) REFERENCES recommendation.rc_recommendation_task(task_id) ON DELETE CASCADE -); -COMMENT ON TABLE recommendation.rc_recommendation_item IS '推荐结果明细表'; -COMMENT ON COLUMN recommendation.rc_recommendation_item.item_id IS '推荐项ID'; -COMMENT ON COLUMN recommendation.rc_recommendation_item.task_id IS '推荐任务ID'; -COMMENT ON COLUMN recommendation.rc_recommendation_item.content_type IS '内容类型'; -COMMENT ON COLUMN recommendation.rc_recommendation_item.content_object_id IS '内容对象ID'; -COMMENT ON COLUMN recommendation.rc_recommendation_item.content_object_type IS '内容对象类型'; -COMMENT ON COLUMN recommendation.rc_recommendation_item.rank_no IS '推荐位次'; -COMMENT ON COLUMN recommendation.rc_recommendation_item.score IS '推荐分'; -COMMENT ON COLUMN recommendation.rc_recommendation_item.reason_codes IS '推荐原因编码集合'; -COMMENT ON COLUMN recommendation.rc_recommendation_item.metadata_json IS '扩展元数据JSON'; -COMMENT ON COLUMN recommendation.rc_recommendation_item.tenant_id IS '租户ID'; -COMMENT ON COLUMN recommendation.rc_recommendation_item.created_at IS '创建时间'; - -DROP TABLE IF EXISTS recommendation.rc_recommendation_feedback CASCADE; -CREATE TABLE IF NOT EXISTS recommendation.rc_recommendation_feedback ( - feedback_id VARCHAR(64) PRIMARY KEY, - item_id VARCHAR(64) NOT NULL, - user_id VARCHAR(64) NOT NULL, - event_type VARCHAR(16) NOT NULL, - event_value NUMERIC(10,4), - event_detail_json JSONB NOT NULL DEFAULT '{}'::JSONB, - event_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - tenant_id VARCHAR(64) NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT chk_rc_recommendation_feedback_event_type - CHECK (event_type IN ('EXPOSE', 'CLICK', 'START', 'COMPLETE', 'DISLIKE', 'SKIP')), - CONSTRAINT chk_rc_recommendation_feedback_detail_json - CHECK (jsonb_typeof(event_detail_json) = 'object'), - CONSTRAINT fk_rc_recommendation_feedback_item - FOREIGN KEY (item_id) REFERENCES recommendation.rc_recommendation_item(item_id) ON DELETE CASCADE, - CONSTRAINT fk_rc_recommendation_feedback_user - FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id) -); -COMMENT ON TABLE recommendation.rc_recommendation_feedback IS '推荐反馈事件表'; -COMMENT ON COLUMN recommendation.rc_recommendation_feedback.feedback_id IS '反馈事件ID'; -COMMENT ON COLUMN recommendation.rc_recommendation_feedback.item_id IS '推荐项ID'; -COMMENT ON COLUMN recommendation.rc_recommendation_feedback.user_id IS '用户ID'; -COMMENT ON COLUMN recommendation.rc_recommendation_feedback.event_type IS '事件类型'; -COMMENT ON COLUMN recommendation.rc_recommendation_feedback.event_value IS '事件值(可选)'; -COMMENT ON COLUMN recommendation.rc_recommendation_feedback.event_detail_json IS '事件详情JSON'; -COMMENT ON COLUMN recommendation.rc_recommendation_feedback.event_time IS '事件发生时间'; -COMMENT ON COLUMN recommendation.rc_recommendation_feedback.tenant_id IS '租户ID'; -COMMENT ON COLUMN recommendation.rc_recommendation_feedback.created_at IS '创建时间'; - -DROP TABLE IF EXISTS recommendation.rc_recommendation_effect_daily CASCADE; -CREATE TABLE IF NOT EXISTS recommendation.rc_recommendation_effect_daily ( - stat_date DATE NOT NULL, - scene_code VARCHAR(32) NOT NULL, - strategy_version VARCHAR(32) NOT NULL, - tenant_id VARCHAR(64) NOT NULL, - expose_count BIGINT NOT NULL DEFAULT 0, - click_count BIGINT NOT NULL DEFAULT 0, - complete_count BIGINT NOT NULL DEFAULT 0, - dislike_count BIGINT NOT NULL DEFAULT 0, - ctr NUMERIC(10,6) NOT NULL DEFAULT 0, - complete_rate NUMERIC(10,6) NOT NULL DEFAULT 0, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (stat_date, scene_code, strategy_version, tenant_id) -); -COMMENT ON TABLE recommendation.rc_recommendation_effect_daily IS '推荐效果日统计表'; -COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.stat_date IS '统计日期'; -COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.scene_code IS '推荐场景编码'; -COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.strategy_version IS '策略版本'; -COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.tenant_id IS '租户ID'; -COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.expose_count IS '曝光次数'; -COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.click_count IS '点击次数'; -COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.complete_count IS '完成次数'; -COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.dislike_count IS '不喜欢次数'; -COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.ctr IS '点击率'; -COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.complete_rate IS '完成率'; -COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.created_at IS '创建时间'; -COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.updated_at IS '更新时间'; - -CREATE INDEX IF NOT EXISTS idx_rc_recommendation_task_user_scene - ON recommendation.rc_recommendation_task(user_id, scene_code, created_at DESC); -CREATE INDEX IF NOT EXISTS idx_rc_recommendation_task_tenant_status - ON recommendation.rc_recommendation_task(tenant_id, status, created_at DESC); -CREATE INDEX IF NOT EXISTS idx_rc_recommendation_item_task - ON recommendation.rc_recommendation_item(task_id, rank_no); -CREATE INDEX IF NOT EXISTS idx_rc_recommendation_item_content - ON recommendation.rc_recommendation_item(content_object_type, content_object_id); -CREATE INDEX IF NOT EXISTS idx_rc_recommendation_feedback_item_event - ON recommendation.rc_recommendation_feedback(item_id, event_type, event_time DESC); -CREATE INDEX IF NOT EXISTS idx_rc_recommendation_feedback_user_event - ON recommendation.rc_recommendation_feedback(user_id, event_type, event_time DESC); -CREATE INDEX IF NOT EXISTS idx_rc_recommendation_effect_daily_tenant_date - ON recommendation.rc_recommendation_effect_daily(tenant_id, stat_date DESC); - -DROP TABLE IF EXISTS recommendation.rc_student_profile_feature CASCADE; -DROP TABLE IF EXISTS recommendation.rc_student_profile_snapshot CASCADE; -CREATE TABLE IF NOT EXISTS recommendation.rc_student_profile_snapshot ( - profile_id VARCHAR(64) PRIMARY KEY, - user_id VARCHAR(64) NOT NULL, - profile_version VARCHAR(32) NOT NULL, - profile_date DATE NOT NULL, - feature_source_version VARCHAR(64), - profile_json JSONB NOT NULL DEFAULT '{}'::JSONB, - confidence_score NUMERIC(6,4), - profile_status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE', - tenant_id VARCHAR(64) NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT uq_rc_student_profile_snapshot_user_version - UNIQUE (user_id, profile_version), - CONSTRAINT chk_rc_student_profile_snapshot_json - CHECK (jsonb_typeof(profile_json) = 'object'), - CONSTRAINT chk_rc_student_profile_snapshot_status - CHECK (profile_status IN ('ACTIVE', 'ARCHIVED')), - CONSTRAINT fk_rc_student_profile_snapshot_user - FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id) -); -COMMENT ON TABLE recommendation.rc_student_profile_snapshot IS '学员画像快照表'; -COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.profile_id IS '画像ID'; -COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.user_id IS '学员ID'; -COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.profile_version IS '画像版本'; -COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.profile_date IS '画像日期'; -COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.feature_source_version IS '特征来源版本'; -COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.profile_json IS '画像JSON'; -COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.confidence_score IS '画像置信度'; -COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.profile_status IS '画像状态'; -COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.tenant_id IS '租户ID'; -COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.created_at IS '创建时间'; -COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.updated_at IS '更新时间'; - -CREATE TABLE IF NOT EXISTS recommendation.rc_student_profile_feature ( - feature_id VARCHAR(64) PRIMARY KEY, - profile_id VARCHAR(64) NOT NULL, - feature_group VARCHAR(32) NOT NULL, - feature_key VARCHAR(64) NOT NULL, - feature_value_num NUMERIC(18,6), - feature_value_text VARCHAR(512), - feature_value_json JSONB, - value_type VARCHAR(16) NOT NULL DEFAULT 'NUM', - source_domain VARCHAR(32) NOT NULL, - source_ref_id VARCHAR(64), - sample_time TIMESTAMP, - tenant_id VARCHAR(64) NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT chk_rc_student_profile_feature_value_type - CHECK (value_type IN ('NUM', 'TEXT', 'JSON', 'BOOL')), - CONSTRAINT chk_rc_student_profile_feature_source_domain - CHECK (source_domain IN ('COURSE', 'HOMEWORK', 'GRADING', 'REVIEW', 'RECOMMENDATION', 'ACHIEVEMENT', 'SYSTEM')), - CONSTRAINT chk_rc_student_profile_feature_json - CHECK (feature_value_json IS NULL OR jsonb_typeof(feature_value_json) IN ('object', 'array', 'string', 'number', 'boolean')), - CONSTRAINT fk_rc_student_profile_feature_profile - FOREIGN KEY (profile_id) REFERENCES recommendation.rc_student_profile_snapshot(profile_id) ON DELETE CASCADE -); -COMMENT ON TABLE recommendation.rc_student_profile_feature IS '学员画像特征明细表'; -COMMENT ON COLUMN recommendation.rc_student_profile_feature.feature_id IS '特征ID'; -COMMENT ON COLUMN recommendation.rc_student_profile_feature.profile_id IS '画像ID'; -COMMENT ON COLUMN recommendation.rc_student_profile_feature.feature_group IS '特征组'; -COMMENT ON COLUMN recommendation.rc_student_profile_feature.feature_key IS '特征键'; -COMMENT ON COLUMN recommendation.rc_student_profile_feature.feature_value_num IS '数值特征值'; -COMMENT ON COLUMN recommendation.rc_student_profile_feature.feature_value_text IS '文本特征值'; -COMMENT ON COLUMN recommendation.rc_student_profile_feature.feature_value_json IS 'JSON特征值'; -COMMENT ON COLUMN recommendation.rc_student_profile_feature.value_type IS '值类型'; -COMMENT ON COLUMN recommendation.rc_student_profile_feature.source_domain IS '来源域'; -COMMENT ON COLUMN recommendation.rc_student_profile_feature.source_ref_id IS '来源对象ID'; -COMMENT ON COLUMN recommendation.rc_student_profile_feature.sample_time IS '采样时间'; -COMMENT ON COLUMN recommendation.rc_student_profile_feature.tenant_id IS '租户ID'; -COMMENT ON COLUMN recommendation.rc_student_profile_feature.created_at IS '创建时间'; - -DROP TABLE IF EXISTS recommendation.rc_learning_loop_event CASCADE; -DROP TABLE IF EXISTS recommendation.rc_learning_loop_case CASCADE; -CREATE TABLE IF NOT EXISTS recommendation.rc_learning_loop_case ( - loop_case_id VARCHAR(64) PRIMARY KEY, - user_id VARCHAR(64) NOT NULL, - wrong_question_id VARCHAR(64) NOT NULL, - review_plan_id VARCHAR(64), - profile_id VARCHAR(64), - recommendation_task_id VARCHAR(64), - recommendation_item_id VARCHAR(64), - feedback_id VARCHAR(64), - user_achievement_id VARCHAR(64), - loop_status VARCHAR(16) NOT NULL DEFAULT 'OPEN', - effect_score NUMERIC(10,4), - opened_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - closed_at TIMESTAMP, - tenant_id VARCHAR(64) NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT chk_rc_learning_loop_case_status - CHECK (loop_status IN ('OPEN', 'IN_PROGRESS', 'CLOSED', 'FAILED')), - CONSTRAINT chk_rc_learning_loop_case_closed_at - CHECK (closed_at IS NULL OR closed_at >= opened_at), - CONSTRAINT fk_rc_learning_loop_case_user - FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id), - CONSTRAINT fk_rc_learning_loop_case_wrong_question - FOREIGN KEY (wrong_question_id) REFERENCES question.gd_wrong_question(wrong_question_id), - CONSTRAINT fk_rc_learning_loop_case_review_plan - FOREIGN KEY (review_plan_id) REFERENCES question.gd_review_plan(review_plan_id), - CONSTRAINT fk_rc_learning_loop_case_profile - FOREIGN KEY (profile_id) REFERENCES recommendation.rc_student_profile_snapshot(profile_id), - CONSTRAINT fk_rc_learning_loop_case_recommendation_task - FOREIGN KEY (recommendation_task_id) REFERENCES recommendation.rc_recommendation_task(task_id), - CONSTRAINT fk_rc_learning_loop_case_recommendation_item - FOREIGN KEY (recommendation_item_id) REFERENCES recommendation.rc_recommendation_item(item_id), - CONSTRAINT fk_rc_learning_loop_case_feedback - FOREIGN KEY (feedback_id) REFERENCES recommendation.rc_recommendation_feedback(feedback_id), - CONSTRAINT fk_rc_learning_loop_case_user_achievement - FOREIGN KEY (user_achievement_id) REFERENCES achievement.ac_user_achievement(user_achievement_id) -); -COMMENT ON TABLE recommendation.rc_learning_loop_case IS '学习闭环主表(错题-复习-推荐-成就)'; -COMMENT ON COLUMN recommendation.rc_learning_loop_case.loop_case_id IS '闭环案例ID'; -COMMENT ON COLUMN recommendation.rc_learning_loop_case.user_id IS '用户ID'; -COMMENT ON COLUMN recommendation.rc_learning_loop_case.wrong_question_id IS '错题ID'; -COMMENT ON COLUMN recommendation.rc_learning_loop_case.review_plan_id IS '复习计划ID'; -COMMENT ON COLUMN recommendation.rc_learning_loop_case.profile_id IS '画像ID'; -COMMENT ON COLUMN recommendation.rc_learning_loop_case.recommendation_task_id IS '推荐任务ID'; -COMMENT ON COLUMN recommendation.rc_learning_loop_case.recommendation_item_id IS '推荐项ID'; -COMMENT ON COLUMN recommendation.rc_learning_loop_case.feedback_id IS '反馈事件ID'; -COMMENT ON COLUMN recommendation.rc_learning_loop_case.user_achievement_id IS '成就记录ID'; -COMMENT ON COLUMN recommendation.rc_learning_loop_case.loop_status IS '闭环状态'; -COMMENT ON COLUMN recommendation.rc_learning_loop_case.effect_score IS '闭环效果分'; -COMMENT ON COLUMN recommendation.rc_learning_loop_case.opened_at IS '闭环开启时间'; -COMMENT ON COLUMN recommendation.rc_learning_loop_case.closed_at IS '闭环关闭时间'; -COMMENT ON COLUMN recommendation.rc_learning_loop_case.tenant_id IS '租户ID'; -COMMENT ON COLUMN recommendation.rc_learning_loop_case.created_at IS '创建时间'; -COMMENT ON COLUMN recommendation.rc_learning_loop_case.updated_at IS '更新时间'; - -CREATE TABLE IF NOT EXISTS recommendation.rc_learning_loop_event ( - loop_event_id VARCHAR(64) PRIMARY KEY, - loop_case_id VARCHAR(64) NOT NULL, - stage_code VARCHAR(32) NOT NULL, - stage_status VARCHAR(16) NOT NULL DEFAULT 'DONE', - event_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - event_score NUMERIC(10,4), - event_detail_json JSONB NOT NULL DEFAULT '{}'::JSONB, - tenant_id VARCHAR(64) NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT chk_rc_learning_loop_event_stage_code - CHECK (stage_code IN ('WRONG_CAPTURED', 'REVIEW_PLANNED', 'PROFILE_UPDATED', 'RECOMMENDED', 'EXPOSED', 'ENGAGED', 'REVIEW_COMPLETED', 'ACHIEVED')), - CONSTRAINT chk_rc_learning_loop_event_stage_status - CHECK (stage_status IN ('PENDING', 'DONE', 'FAILED', 'SKIPPED')), - CONSTRAINT chk_rc_learning_loop_event_detail_json - CHECK (jsonb_typeof(event_detail_json) = 'object'), - CONSTRAINT fk_rc_learning_loop_event_case - FOREIGN KEY (loop_case_id) REFERENCES recommendation.rc_learning_loop_case(loop_case_id) ON DELETE CASCADE -); -COMMENT ON TABLE recommendation.rc_learning_loop_event IS '学习闭环阶段事件表'; -COMMENT ON COLUMN recommendation.rc_learning_loop_event.loop_event_id IS '闭环事件ID'; -COMMENT ON COLUMN recommendation.rc_learning_loop_event.loop_case_id IS '闭环案例ID'; -COMMENT ON COLUMN recommendation.rc_learning_loop_event.stage_code IS '阶段编码'; -COMMENT ON COLUMN recommendation.rc_learning_loop_event.stage_status IS '阶段状态'; -COMMENT ON COLUMN recommendation.rc_learning_loop_event.event_time IS '事件时间'; -COMMENT ON COLUMN recommendation.rc_learning_loop_event.event_score IS '阶段得分'; -COMMENT ON COLUMN recommendation.rc_learning_loop_event.event_detail_json IS '事件详情JSON'; -COMMENT ON COLUMN recommendation.rc_learning_loop_event.tenant_id IS '租户ID'; -COMMENT ON COLUMN recommendation.rc_learning_loop_event.created_at IS '创建时间'; - -DROP TABLE IF EXISTS recommendation.rc_learning_loop_effect_daily CASCADE; -CREATE TABLE IF NOT EXISTS recommendation.rc_learning_loop_effect_daily ( - stat_date DATE NOT NULL, - tenant_id VARCHAR(64) NOT NULL, - opened_case_count BIGINT NOT NULL DEFAULT 0, - closed_case_count BIGINT NOT NULL DEFAULT 0, - achieved_case_count BIGINT NOT NULL DEFAULT 0, - avg_effect_score NUMERIC(10,4) NOT NULL DEFAULT 0, - avg_close_hours NUMERIC(10,4) NOT NULL DEFAULT 0, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (stat_date, tenant_id) -); -COMMENT ON TABLE recommendation.rc_learning_loop_effect_daily IS '学习闭环效果日统计表'; -COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.stat_date IS '统计日期'; -COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.tenant_id IS '租户ID'; -COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.opened_case_count IS '开启案例数'; -COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.closed_case_count IS '关闭案例数'; -COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.achieved_case_count IS '达成成就案例数'; -COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.avg_effect_score IS '平均效果分'; -COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.avg_close_hours IS '平均闭环时长(小时)'; -COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.created_at IS '创建时间'; -COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.updated_at IS '更新时间'; - -CREATE INDEX IF NOT EXISTS idx_rc_student_profile_snapshot_user_date - ON recommendation.rc_student_profile_snapshot(user_id, profile_date DESC); -CREATE INDEX IF NOT EXISTS idx_rc_student_profile_snapshot_tenant_version - ON recommendation.rc_student_profile_snapshot(tenant_id, profile_version, created_at DESC); -CREATE INDEX IF NOT EXISTS idx_rc_student_profile_feature_profile_group - ON recommendation.rc_student_profile_feature(profile_id, feature_group, feature_key); -CREATE INDEX IF NOT EXISTS idx_rc_student_profile_feature_source - ON recommendation.rc_student_profile_feature(source_domain, source_ref_id); -CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_case_user_status - ON recommendation.rc_learning_loop_case(user_id, loop_status, opened_at DESC); -CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_case_wrong_question - ON recommendation.rc_learning_loop_case(wrong_question_id, opened_at DESC); -CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_case_tenant_status - ON recommendation.rc_learning_loop_case(tenant_id, loop_status, opened_at DESC); -CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_event_case_stage_time - ON recommendation.rc_learning_loop_event(loop_case_id, stage_code, event_time DESC); -CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_event_tenant_time - ON recommendation.rc_learning_loop_event(tenant_id, event_time DESC); -CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_effect_daily_tenant_date - ON recommendation.rc_learning_loop_effect_daily(tenant_id, stat_date DESC); - -DROP TABLE IF EXISTS recommendation.rc_loop_metric_map CASCADE; -DROP TABLE IF EXISTS recommendation.rc_loop_stage_achievement_map CASCADE; -CREATE TABLE IF NOT EXISTS recommendation.rc_loop_stage_achievement_map ( - stage_map_id VARCHAR(64) PRIMARY KEY, - stage_code VARCHAR(32) NOT NULL, - event_code VARCHAR(64) NOT NULL, - template_id VARCHAR(64), - metric_id VARCHAR(64), - award_source VARCHAR(32) NOT NULL DEFAULT 'RULE_ENGINE', - priority INTEGER NOT NULL DEFAULT 100, - enabled BOOLEAN NOT NULL DEFAULT TRUE, - ext_json JSONB NOT NULL DEFAULT '{}'::JSONB, - tenant_id VARCHAR(64) NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT uq_rc_loop_stage_achievement_map - UNIQUE (tenant_id, stage_code, event_code, priority), - CONSTRAINT chk_rc_loop_stage_achievement_map_stage_code - CHECK (stage_code IN ('WRONG_CAPTURED', 'REVIEW_PLANNED', 'PROFILE_UPDATED', 'RECOMMENDED', 'EXPOSED', 'ENGAGED', 'REVIEW_COMPLETED', 'ACHIEVED')), - CONSTRAINT chk_rc_loop_stage_achievement_map_award_source - CHECK (award_source IN ('RULE_ENGINE', 'MANUAL', 'SYSTEM')), - CONSTRAINT chk_rc_loop_stage_achievement_map_ext_json - CHECK (jsonb_typeof(ext_json) = 'object'), - CONSTRAINT fk_rc_loop_stage_achievement_map_event - FOREIGN KEY (event_code) REFERENCES achievement.ac_achievement_event_dict(event_code), - CONSTRAINT fk_rc_loop_stage_achievement_map_template - FOREIGN KEY (template_id) REFERENCES achievement.ac_achievement_rule_template(template_id), - CONSTRAINT fk_rc_loop_stage_achievement_map_metric - FOREIGN KEY (metric_id) REFERENCES achievement.ac_achievement_metric_def(metric_id) -); -COMMENT ON TABLE recommendation.rc_loop_stage_achievement_map IS '闭环阶段到成就事件映射表(通用配置)'; -COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.stage_map_id IS '阶段映射ID'; -COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.stage_code IS '闭环阶段编码'; -COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.event_code IS '成就事件编码'; -COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.template_id IS '成就规则模板ID'; -COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.metric_id IS '成就指标ID'; -COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.award_source IS '发放来源'; -COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.priority IS '优先级'; -COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.enabled IS '是否启用'; -COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.ext_json IS '扩展配置JSON'; -COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.tenant_id IS '租户ID'; -COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.created_at IS '创建时间'; -COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.updated_at IS '更新时间'; - -CREATE TABLE IF NOT EXISTS recommendation.rc_loop_metric_map ( - metric_map_id VARCHAR(64) PRIMARY KEY, - stage_code VARCHAR(32) NOT NULL, - metric_id VARCHAR(64) NOT NULL, - source_field VARCHAR(64) NOT NULL, - agg_method VARCHAR(16) NOT NULL DEFAULT 'LATEST', - weight NUMERIC(6,4) NOT NULL DEFAULT 1.0000, - enabled BOOLEAN NOT NULL DEFAULT TRUE, - ext_json JSONB NOT NULL DEFAULT '{}'::JSONB, - tenant_id VARCHAR(64) NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT uq_rc_loop_metric_map - UNIQUE (tenant_id, stage_code, metric_id, source_field), - CONSTRAINT chk_rc_loop_metric_map_stage_code - CHECK (stage_code IN ('WRONG_CAPTURED', 'REVIEW_PLANNED', 'PROFILE_UPDATED', 'RECOMMENDED', 'EXPOSED', 'ENGAGED', 'REVIEW_COMPLETED', 'ACHIEVED')), - CONSTRAINT chk_rc_loop_metric_map_agg_method - CHECK (agg_method IN ('LATEST', 'SUM', 'AVG', 'MAX', 'MIN', 'COUNT')), - CONSTRAINT chk_rc_loop_metric_map_weight - CHECK (weight > 0 AND weight <= 1), - CONSTRAINT chk_rc_loop_metric_map_ext_json - CHECK (jsonb_typeof(ext_json) = 'object'), - CONSTRAINT fk_rc_loop_metric_map_metric - FOREIGN KEY (metric_id) REFERENCES achievement.ac_achievement_metric_def(metric_id) -); -COMMENT ON TABLE recommendation.rc_loop_metric_map IS '闭环指标映射表(通用配置)'; -COMMENT ON COLUMN recommendation.rc_loop_metric_map.metric_map_id IS '指标映射ID'; -COMMENT ON COLUMN recommendation.rc_loop_metric_map.stage_code IS '闭环阶段编码'; -COMMENT ON COLUMN recommendation.rc_loop_metric_map.metric_id IS '成就指标ID'; -COMMENT ON COLUMN recommendation.rc_loop_metric_map.source_field IS '来源字段名'; -COMMENT ON COLUMN recommendation.rc_loop_metric_map.agg_method IS '聚合方法'; -COMMENT ON COLUMN recommendation.rc_loop_metric_map.weight IS '权重'; -COMMENT ON COLUMN recommendation.rc_loop_metric_map.enabled IS '是否启用'; -COMMENT ON COLUMN recommendation.rc_loop_metric_map.ext_json IS '扩展配置JSON'; -COMMENT ON COLUMN recommendation.rc_loop_metric_map.tenant_id IS '租户ID'; -COMMENT ON COLUMN recommendation.rc_loop_metric_map.created_at IS '创建时间'; -COMMENT ON COLUMN recommendation.rc_loop_metric_map.updated_at IS '更新时间'; - -CREATE INDEX IF NOT EXISTS idx_rc_loop_stage_achievement_map_tenant_stage - ON recommendation.rc_loop_stage_achievement_map(tenant_id, stage_code, enabled, priority); -CREATE INDEX IF NOT EXISTS idx_rc_loop_stage_achievement_map_event - ON recommendation.rc_loop_stage_achievement_map(event_code, tenant_id, enabled); -CREATE INDEX IF NOT EXISTS idx_rc_loop_metric_map_tenant_stage - ON recommendation.rc_loop_metric_map(tenant_id, stage_code, enabled);