sql更新、架构更新

This commit is contained in:
2026-04-16 15:46:29 +08:00
parent 2f2d796e30
commit d5c06eca28
36 changed files with 2099 additions and 387415 deletions

View File

@@ -6,7 +6,7 @@
"service": "workspace", "service": "workspace",
"workspaceFolder": "/workspaces/K12study", "workspaceFolder": "/workspaces/K12study",
"shutdownAction": "stopCompose", "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": { "customizations": {
"vscode": { "vscode": {
"settings": { "settings": {

View File

@@ -18,6 +18,13 @@ services:
K12STUDY_REGISTRY_PORT: ${K12STUDY_REGISTRY_PORT:-8848} K12STUDY_REGISTRY_PORT: ${K12STUDY_REGISTRY_PORT:-8848}
K12STUDY_REGISTRY_GRPC_PORT: ${K12STUDY_REGISTRY_GRPC_PORT:-9848} K12STUDY_REGISTRY_GRPC_PORT: ${K12STUDY_REGISTRY_GRPC_PORT:-9848}
K12STUDY_REGISTRY_RAFT_PORT: ${K12STUDY_REGISTRY_RAFT_PORT:-9849} 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" PYTHONUNBUFFERED: "1"
extra_hosts: extra_hosts:
- "host.docker.internal:host-gateway" - "host.docker.internal:host-gateway"

View File

@@ -5,9 +5,20 @@
], ],
"service": "workspace", "service": "workspace",
"workspaceFolder": "/workspaces/K12study", "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", "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": { "customizations": {
"vscode": { "vscode": {
"settings": { "settings": {

View File

@@ -14,10 +14,24 @@ services:
K12STUDY_REDIS_HOST: redis K12STUDY_REDIS_HOST: redis
K12STUDY_REDIS_PORT: 6379 K12STUDY_REDIS_PORT: 6379
K12STUDY_REDIS_PASSWORD: "" 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" PYTHONUNBUFFERED: "1"
depends_on: depends_on:
- postgres - postgres
- redis - redis
- nacos
- nebula-graphd
- milvus
postgres: postgres:
image: postgres:16 image: postgres:16
@@ -40,7 +54,109 @@ services:
command: redis-server --appendonly yes command: redis-server --appendonly yes
volumes: volumes:
- redis-dev-data:/data - 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: volumes:
postgres-dev-data: postgres-dev-data:
redis-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:

View File

@@ -61,6 +61,102 @@ services:
- ${K12STUDY_RUN_DIR:-./runtime}/nacos/data:/home/nacos/data - ${K12STUDY_RUN_DIR:-./runtime}/nacos/data:/home/nacos/data
networks: networks:
- k12study-dev-net - 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: networks:
k12study-dev-net: k12study-dev-net:

View File

@@ -23,7 +23,116 @@ services:
command: redis-server --appendonly yes command: redis-server --appendonly yes
volumes: volumes:
- redis-data:/data - 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: volumes:
postgres-data: postgres-data:
redis-data: redis-data:
nacos-logs:
nacos-data:
nebula-meta-data:
nebula-storage-data:
milvus-etcd-data:
milvus-minio-data:
milvus-data:

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 5.4 MiB

View File

@@ -1,178 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
<key for="node" id="nodegraph" yfiles.type="nodegraphics"/>
<key for="edge" id="edgegraph" yfiles.type="edgegraphics"/>
<graph edgedefault="directed" id="G">
<node id="entity0">
<data key="nodegraph">
<y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
<y:Geometry height="370" width="421.48" x="440" y="40"/>
<y:Fill color="#FFFFE1" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" configuration="com.yworks.entityRelationship.label.name" fontFamily="Courier" fontSize="12" fontStyle="plain" hasLineColor="false" modelName="internal" modelPosition="t" backgroundColor="#FFFFE1" textColor="#FFFFFF" visible="true" horizontalTextPosition="center" iconTextGap="4" height="22" width="357" x="0" y="4">tb_ai_graph_entity</y:NodeLabel>
<y:NodeLabel alignment="left" autoSizePolicy="content" configuration="com.yworks.entityRelationship.label.attributes" fontFamily="Courier" fontSize="12" fontStyle="plain" hasLineColor="false" modelName="custom" modelPosition="t" backgroundColor="#FFFFFF" textColor="#000000" visible="true" horizontalTextPosition="center" iconTextGap="4" height="304" width="357" x="2" y="31.66796875">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 - 更新时间 <y:LabelModel>
<y:ErdAttributesNodeLabelModel/>
</y:LabelModel>
<y:ModelParameter>
<y:ErdAttributesNodeLabelModelParameter/>
</y:ModelParameter>
</y:NodeLabel>
</y:GenericNode>
</data>
</node>
<node id="entity1">
<data key="nodegraph">
<y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
<y:Geometry height="390" width="411.24" x="40" y="40"/>
<y:Fill color="#FFFFE1" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" configuration="com.yworks.entityRelationship.label.name" fontFamily="Courier" fontSize="12" fontStyle="plain" hasLineColor="false" modelName="internal" modelPosition="t" backgroundColor="#FFFFE1" textColor="#FFFFFF" visible="true" horizontalTextPosition="center" iconTextGap="4" height="22" width="341" x="0" y="4">tb_ai_graph_relation</y:NodeLabel>
<y:NodeLabel alignment="left" autoSizePolicy="content" configuration="com.yworks.entityRelationship.label.attributes" fontFamily="Courier" fontSize="12" fontStyle="plain" hasLineColor="false" modelName="custom" modelPosition="t" backgroundColor="#FFFFFF" textColor="#000000" visible="true" horizontalTextPosition="center" iconTextGap="4" height="324" width="341" x="2" y="31.66796875">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 - 更新时间 <y:LabelModel>
<y:ErdAttributesNodeLabelModel/>
</y:LabelModel>
<y:ModelParameter>
<y:ErdAttributesNodeLabelModelParameter/>
</y:ModelParameter>
</y:NodeLabel>
</y:GenericNode>
</data>
</node>
<node id="entity2">
<data key="nodegraph">
<y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
<y:Geometry height="430" width="545.28" x="940" y="220"/>
<y:Fill color="#FFFFE1" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" configuration="com.yworks.entityRelationship.label.name" fontFamily="Courier" fontSize="12" fontStyle="plain" hasLineColor="false" modelName="internal" modelPosition="t" backgroundColor="#FFFFE1" textColor="#FFFFFF" visible="true" horizontalTextPosition="center" iconTextGap="4" height="22" width="452" x="0" y="4">tb_ai_knowledge_file</y:NodeLabel>
<y:NodeLabel alignment="left" autoSizePolicy="content" configuration="com.yworks.entityRelationship.label.attributes" fontFamily="Courier" fontSize="12" fontStyle="plain" hasLineColor="false" modelName="custom" modelPosition="t" backgroundColor="#FFFFFF" textColor="#000000" visible="true" horizontalTextPosition="center" iconTextGap="4" height="364" width="452" x="2" y="31.66796875">file_id: varchar(64) NOT NULL - 知识文件ID
file_code: varchar(64) NOT NULL - 知识文件编码
file_name: varchar(255) NOT NULL - 知识文件名称
source_file_id: varchar(64) - 来源文件IDupms.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 - 更新时间 <y:LabelModel>
<y:ErdAttributesNodeLabelModel/>
</y:LabelModel>
<y:ModelParameter>
<y:ErdAttributesNodeLabelModelParameter/>
</y:ModelParameter>
</y:NodeLabel>
</y:GenericNode>
</data>
</node>
<node id="entity3">
<data key="nodegraph">
<y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
<y:Geometry height="350" width="520.84" x="440" y="480"/>
<y:Fill color="#FFFFE1" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" configuration="com.yworks.entityRelationship.label.name" fontFamily="Courier" fontSize="12" fontStyle="plain" hasLineColor="false" modelName="internal" modelPosition="t" backgroundColor="#FFFFE1" textColor="#FFFFFF" visible="true" horizontalTextPosition="center" iconTextGap="4" height="22" width="429" x="0" y="4">tb_ai_knowledge_sync_task</y:NodeLabel>
<y:NodeLabel alignment="left" autoSizePolicy="content" configuration="com.yworks.entityRelationship.label.attributes" fontFamily="Courier" fontSize="12" fontStyle="plain" hasLineColor="false" modelName="custom" modelPosition="t" backgroundColor="#FFFFFF" textColor="#000000" visible="true" horizontalTextPosition="center" iconTextGap="4" height="284" width="429" x="2" y="31.66796875">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 - 更新时间 <y:LabelModel>
<y:ErdAttributesNodeLabelModel/>
</y:LabelModel>
<y:ModelParameter>
<y:ErdAttributesNodeLabelModelParameter/>
</y:ModelParameter>
</y:NodeLabel>
</y:GenericNode>
</data>
</node>
<edge id="edge0-0" source="entity0" target="entity2">
<data key="edgegraph">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="dashed" width="1.0"/>
<y:Arrows source="white_diamond" target="circle"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="edge1-0" source="entity1" target="entity0">
<data key="edgegraph">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="dashed" width="1.0"/>
<y:Arrows source="white_diamond" target="circle"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="edge1-1" source="entity1" target="entity0">
<data key="edgegraph">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="dashed" width="1.0"/>
<y:Arrows source="white_diamond" target="circle"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
<edge id="edge3-0" source="entity3" target="entity2">
<data key="edgegraph">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
<y:LineStyle color="#000000" type="dashed" width="1.0"/>
<y:Arrows source="white_diamond" target="circle"/>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
</graph>
</graphml>

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 978 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 6.6 MiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 9.3 MiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 5.7 MiB

View File

@@ -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业务主数据与事实源
- 缓存Redistoken、权限、热点缓存
- 图数据库NebulaGraph主选Neo4j Community备选 ## 3. 模块化里程碑分解
- 向量库Milvus ### M0 逻辑视图梳理04.13-04.15
- 关系库PostgreSQL存储原始业务表 - 目标
- 固化系统分层、边界与主链路,避免后续并行开发职责交叉。
- 关键任务
- 统一 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业务主数据与原始表数据唯一事实源 ### M2 基础服务配置04.22-04.26
- NebulaGraph / Neo4j知识点、题目、学生作答、知识关系等图结构查询 - 目标
- Milvus教材/题解/知识片段向量检索与语义召回 - 打通基础运行底座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 段)
- 关系库到图库:通过同步任务进行实体与关系映射 ### M4 基础架构前后端代码构建05.04-05.10
- 关系库到向量库:通过切片与 embedding 任务进行索引 - 目标
- 图谱节点保留源表标识(如 `source_table``source_pk`),支持点击节点回查原始表数据 - 实现并联调基础架构链路(登录、当前用户、动态路由、区域树、租户树、部门树)。
- 关键任务
- 后端auth/upms 与网关链路稳定化。
- 前端React 管理端基础框架与动态路由消费。
- 小程序:基础请求层与最小页面壳可访问基础接口。
- 验收标准
- `/api/auth/*``/api/upms/*` 在本地模式和网关模式都可联调。
## 4. 架构图产物(待补充 ### M5 核心课程/学习业务 API 与数据模型冻结05.11-05.24
- 目标
- 将剩余周期聚焦课程与学习核心内容,完成核心域契约冻结。
- 关键任务
- 完成课程、学习路径、学习记录、学习效果等核心 API 与数据模型定义。
- 固化核心流程输入输出 DTO 与事件流。
- 验收产物
- `docs/architecture/api-design.md`(课程/学习核心段)
- 系统流程图:建设中 ### M6 核心课程/学习业务前后端构建05.25-06.14
- ER 图:建设中 - 目标
- 数据流图:建设中 - 完成课程与学习核心功能前后端落地并联调。
- 关键任务
- 后端:课程与学习域核心能力实现。
- 前端/小程序:课程学习主流程页面与 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`

View File

@@ -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` 执行模块测试与整体测试。

View File

@@ -0,0 +1,82 @@
# API 设计(基础架构 + 业务功能)
## 1. API 设计原则
- 对外统一前缀:`/api/*`
- 统一响应结构:`code/message/data/traceId`
- 认证策略JWT + RBAC网关做统一鉴权透传。
- API 冻结点:
- 基础架构 API 在 M3 冻结。
- 业务 API 在 M5 冻结。
## 2. 基础架构 APIM3
### 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. 业务功能 APIM5
### 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 完成联调,不新增破坏性变更。

View File

@@ -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` 端口连接可用
- MilvusHTTP `/healthz`
- Python AI`GET /health`
## 6. 与现有目录关系
- 现有公共服务编排:`docker/dev-common/docker-compose.public-services.yml`
- 本文新增服务编排样例:`docs/architecture/service-baseline.compose.yaml`

View File

@@ -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。

View File

@@ -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 契约与事件解耦,不直接跨模块读写表。

View File

@@ -13,7 +13,7 @@
<mxCell id="10" value="cl_course&lt;br&gt;PK course_id&lt;br&gt;title, subject_code, grade_code&lt;br&gt;difficulty_level, status&lt;br&gt;tenant_id, adcode, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> <mxCell id="10" value="cl_course&lt;br&gt;PK course_id&lt;br&gt;title, subject_code, grade_code&lt;br&gt;difficulty_level, status&lt;br&gt;tenant_id, adcode, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
<mxGeometry x="50" y="130" width="250" height="130" as="geometry"/> <mxGeometry x="50" y="130" width="250" height="130" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="12" value="cl_course_lesson&lt;br&gt;PK lesson_id&lt;br&gt;FK chapter_id -&gt; cl_course_chapter.chapter_id&lt;br&gt;lesson_no, lesson_title, duration_sec&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> <mxCell id="12" value="cl_course_node&lt;br&gt;PK node_id&lt;br&gt;FK chapter_id -&gt; cl_course_chapter.chapter_id&lt;br&gt;node_no, node_title, node_type, class_type&lt;br&gt;duration_sec, tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
<mxGeometry x="650" y="300" width="270" height="130" as="geometry"/> <mxGeometry x="650" y="300" width="270" height="130" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="13" value="cl_knowledge_point&lt;br&gt;PK kp_id&lt;br&gt;kp_code, kp_name, subject_code, grade_code&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"> <mxCell id="13" value="cl_knowledge_point&lt;br&gt;PK kp_id&lt;br&gt;kp_code, kp_name, subject_code, grade_code&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1">
@@ -22,7 +22,7 @@
<mxCell id="14" value="cl_course_knowledge_rel&lt;br&gt;PK (course_id, kp_id)&lt;br&gt;FK course_id -&gt; cl_course.course_id&lt;br&gt;FK kp_id -&gt; cl_knowledge_point.kp_id&lt;br&gt;weight, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"> <mxCell id="14" value="cl_course_knowledge_rel&lt;br&gt;PK (course_id, kp_id)&lt;br&gt;FK course_id -&gt; cl_course.course_id&lt;br&gt;FK kp_id -&gt; cl_knowledge_point.kp_id&lt;br&gt;weight, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="1680" y="190" width="280" height="130" as="geometry"/> <mxGeometry x="1680" y="190" width="280" height="130" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="15" value="cl_course_resource&lt;br&gt;PK resource_id&lt;br&gt;FK lesson_id -&gt; cl_course_lesson.lesson_id&lt;br&gt;resource_type(pdf/video/doc), resource_url&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> <mxCell id="15" value="cl_node_resource&lt;br&gt;PK resource_id&lt;br&gt;FK node_id -&gt; cl_course_node.node_id&lt;br&gt;resource_type(PDF/VIDEO/DOC/IMAGE/LINK), file_id/resource_url&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
<mxGeometry x="640" y="560" width="290" height="130" as="geometry"/> <mxGeometry x="640" y="560" width="290" height="130" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="16" value="cl_course_tag&lt;br&gt;PK tag_id&lt;br&gt;tag_name, tag_type&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"> <mxCell id="16" value="cl_course_tag&lt;br&gt;PK tag_id&lt;br&gt;tag_name, tag_type&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1">
@@ -34,7 +34,7 @@
<mxCell id="18" value="cl_learning_session&lt;br&gt;PK session_id&lt;br&gt;FK user_id -&gt; tb_sys_user.user_id&lt;br&gt;FK course_id -&gt; cl_course.course_id&lt;br&gt;status(STARTED/PAUSED/COMPLETED)&lt;br&gt;started_at, ended_at, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"> <mxCell id="18" value="cl_learning_session&lt;br&gt;PK session_id&lt;br&gt;FK user_id -&gt; tb_sys_user.user_id&lt;br&gt;FK course_id -&gt; cl_course.course_id&lt;br&gt;status(STARTED/PAUSED/COMPLETED)&lt;br&gt;started_at, ended_at, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
<mxGeometry x="950" y="450" width="300" height="140" as="geometry"/> <mxGeometry x="950" y="450" width="300" height="140" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="19" value="cl_learning_progress&lt;br&gt;PK progress_id&lt;br&gt;FK session_id -&gt; cl_learning_session.session_id&lt;br&gt;FK lesson_id -&gt; cl_course_lesson.lesson_id&lt;br&gt;progress_pct, last_position_sec&lt;br&gt;mastery_level, tenant_id, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"> <mxCell id="19" value="cl_learning_progress&lt;br&gt;PK progress_id&lt;br&gt;FK session_id -&gt; cl_learning_session.session_id&lt;br&gt;FK node_id -&gt; cl_course_node.node_id&lt;br&gt;progress_pct, last_position_sec&lt;br&gt;mastery_level, tenant_id, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
<mxGeometry x="1880" y="295" width="320" height="140" as="geometry"/> <mxGeometry x="1880" y="295" width="320" height="140" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="20" value="cl_learning_event&lt;br&gt;PK event_id&lt;br&gt;FK session_id -&gt; cl_learning_session.session_id&lt;br&gt;event_type(start/pause/seek/finish)&lt;br&gt;event_time, payload_json, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"> <mxCell id="20" value="cl_learning_event&lt;br&gt;PK event_id&lt;br&gt;FK session_id -&gt; cl_learning_session.session_id&lt;br&gt;event_type(start/pause/seek/finish)&lt;br&gt;event_time, payload_json, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
@@ -46,15 +46,18 @@
<mxCell id="22" value="外部同步依赖ai&lt;br&gt;ai.tb_ai_knowledge_file&lt;br&gt;ai.tb_ai_knowledge_sync_task&lt;br&gt;ai.tb_ai_graph_entity / ai.tb_ai_graph_relation" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" parent="1" vertex="1"> <mxCell id="22" value="外部同步依赖ai&lt;br&gt;ai.tb_ai_knowledge_file&lt;br&gt;ai.tb_ai_knowledge_sync_task&lt;br&gt;ai.tb_ai_graph_entity / ai.tb_ai_graph_relation" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" parent="1" vertex="1">
<mxGeometry x="1320" y="760" width="330" height="150" as="geometry"/> <mxGeometry x="1320" y="760" width="330" height="150" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="23" value="cl_kp_prerequisite_rel&lt;br&gt;PK (kp_id, pre_kp_id)&lt;br&gt;FK kp_id -&gt; cl_knowledge_point.kp_id&lt;br&gt;FK pre_kp_id -&gt; cl_knowledge_point.kp_id&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"> <mxCell id="23" value="cl_chapter_kp_rel&lt;br&gt;PK (chapter_id, kp_id)&lt;br&gt;FK chapter_id -&gt; cl_course_chapter.chapter_id&lt;br&gt;FK kp_id -&gt; cl_knowledge_point.kp_id&lt;br&gt;relation_type, weight, is_core, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="1320" y="150" width="340" height="120" as="geometry"/> <mxGeometry x="1320" y="150" width="340" height="120" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="24" value="cl_kp_material_rel&lt;br&gt;PK (kp_id, resource_id)&lt;br&gt;FK kp_id -&gt; cl_knowledge_point.kp_id&lt;br&gt;FK resource_id -&gt; cl_course_resource.resource_id&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"> <mxCell id="24" value="cl_node_kp_rel&lt;br&gt;PK (node_id, kp_id)&lt;br&gt;FK node_id -&gt; cl_course_node.node_id&lt;br&gt;FK kp_id -&gt; cl_knowledge_point.kp_id&lt;br&gt;relation_type, weight, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="1350" y="565" width="360" height="120" as="geometry"/> <mxGeometry x="1350" y="565" width="360" height="120" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="25" value="cl_student_kp_mastery&lt;br&gt;PK mastery_id&lt;br&gt;FK kp_id -&gt; cl_knowledge_point.kp_id&lt;br&gt;student_id, mastery_level, mastery_score&lt;br&gt;last_practiced_at, tenant_id, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"> <mxCell id="25" value="cl_student_kp_mastery&lt;br&gt;PK mastery_id&lt;br&gt;FK kp_id -&gt; cl_knowledge_point.kp_id&lt;br&gt;student_id, mastery_level, mastery_score&lt;br&gt;last_practiced_at, tenant_id, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
<mxGeometry x="1320" y="420" width="360" height="120" as="geometry"/> <mxGeometry x="1320" y="420" width="360" height="120" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="26" value="cl_node_homework_rel&lt;br&gt;PK (node_id, assignment_id)&lt;br&gt;FK node_id -&gt; cl_course_node.node_id&lt;br&gt;assignment_id -&gt; question.hw_assignment.assignment_id&lt;br&gt;relation_type, tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="1710" y="560" width="390" height="120" as="geometry"/>
</mxCell>
<mxCell id="100" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="10" target="11" edge="1"> <mxCell id="100" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="10" target="11" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
@@ -88,13 +91,13 @@
<mxCell id="110" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="18" target="20" edge="1"> <mxCell id="110" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="18" target="20" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="112" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="13" target="23" edge="1"> <mxCell id="112" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="11" target="23" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="113" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="13" target="24" edge="1"> <mxCell id="113" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="13" target="24" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="114" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="15" target="24" edge="1"> <mxCell id="114" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="12" target="24" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="115" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="13" target="25" edge="1"> <mxCell id="115" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="13" target="25" edge="1">
@@ -103,6 +106,9 @@
<mxCell id="116" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="19" target="25" edge="1"> <mxCell id="116" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="19" target="25" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="117" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="12" target="26" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="11" value="cl_course_chapter&lt;br&gt;PK chapter_id&lt;br&gt;FK course_id -&gt; cl_course.course_id&lt;br&gt;chapter_no, chapter_title&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> <mxCell id="11" value="cl_course_chapter&lt;br&gt;PK chapter_id&lt;br&gt;FK course_id -&gt; cl_course.course_id&lt;br&gt;chapter_no, chapter_title&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
<mxGeometry x="340" y="240" width="250" height="130" as="geometry"/> <mxGeometry x="340" y="240" width="250" height="130" as="geometry"/>
</mxCell> </mxCell>

View File

@@ -31,7 +31,7 @@
<mxCell id="60" value="设计方法与原因&lt;br&gt;1) CQRS课程目录读取与学习进度写入分离&lt;br&gt;2) 会话聚合驱动事件化进度更新&lt;br&gt;3) 热点进度走Redis减轻主库读压&lt;br&gt;4) 课程实体与知识点索引分离,支持后续多策略检索&lt;br&gt;5) 同步调度支持 JAR 本地执行与 REST 远程触发" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1"> <mxCell id="60" value="设计方法与原因&lt;br&gt;1) CQRS课程目录读取与学习进度写入分离&lt;br&gt;2) 会话聚合驱动事件化进度更新&lt;br&gt;3) 热点进度走Redis减轻主库读压&lt;br&gt;4) 课程实体与知识点索引分离,支持后续多策略检索&lt;br&gt;5) 同步调度支持 JAR 本地执行与 REST 远程触发" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="1360" y="170" width="360" height="190" as="geometry"/> <mxGeometry x="1360" y="170" width="360" height="190" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="100" value="浏览课程/课时" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="10" target="20" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="100" value="浏览课程/学习节点" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="10" target="20" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="101" value="课程发布/下架" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="11" target="20" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="101" value="课程发布/下架" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="11" target="20" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="102" value="规范校验" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="12" target="20" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="102" value="规范校验" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="12" target="20" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="103" value="课程目录查询" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="20" target="30" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="103" value="课程目录查询" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="20" target="30" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>

View File

@@ -7,34 +7,37 @@
<mxCell id="2" value="02 习题与作业 - ER图question单schema10_create + 20_init" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=22;fontStyle=1;" parent="1" vertex="1"> <mxCell id="2" value="02 习题与作业 - ER图question单schema10_create + 20_init" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=22;fontStyle=1;" parent="1" vertex="1">
<mxGeometry x="20" y="20" width="520" height="30" as="geometry"/> <mxGeometry x="20" y="20" width="520" height="30" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="3" value="说明question 目录仅保留 2 个 SQL 文件10_create_question_tables.sql / 20_init_question_seed.sql" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=12;fontColor=#666666;" parent="1" vertex="1"> <mxCell id="3" value="说明question 目录仅保留 2 个 SQL 文件10_create_question_tables.sql / 20_init_question_seed.sql,推荐 rc_* 表已并入 question schema" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=12;fontColor=#666666;" parent="1" vertex="1">
<mxGeometry x="20" y="52" width="1100" height="24" as="geometry"/> <mxGeometry x="20" y="52" width="1100" height="24" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="10" value="question.hw_question_bank&lt;br&gt;PK bank_id&lt;br&gt;bank_name, subject_code, grade_code, status&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> <mxCell id="10" value="question.hw_question_bank&lt;br&gt;PK bank_id&lt;br&gt;bank_name, subject_code, grade_code, status&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
<mxGeometry x="40" y="90" width="270" height="120" as="geometry"/> <mxGeometry x="40" y="90" width="270" height="120" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="11" value="question.hw_question_item&lt;br&gt;PK question_id&lt;br&gt;FK bank_id -&gt; hw_question_bank.bank_id&lt;br&gt;question_type, stem, stem_json, difficulty&lt;br&gt;answer_payload, analysis, scoring_rule_json&lt;br&gt;question_status, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> <mxCell id="11" value="question.hw_question_item&lt;br&gt;PK question_id&lt;br&gt;question_type, stem, stem_json, difficulty&lt;br&gt;answer_payload, analysis, scoring_rule_json&lt;br&gt;question_status, tenant_id, created_at, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
<mxGeometry x="350" y="80" width="380" height="170" as="geometry"/> <mxGeometry x="350" y="80" width="380" height="170" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="20" value="question.hw_bank_question_rel&lt;br&gt;PK (bank_id, question_id)&lt;br&gt;FK bank_id -&gt; hw_question_bank.bank_id&lt;br&gt;FK question_id -&gt; hw_question_item.question_id&lt;br&gt;question_order, source_type, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="350" y="260" width="380" height="120" as="geometry"/>
</mxCell>
<mxCell id="12" value="question.hw_question_kp_rel&lt;br&gt;PK (question_id, kp_id)&lt;br&gt;FK question_id -&gt; hw_question_item.question_id&lt;br&gt;FK kp_id -&gt; cl_knowledge_point.kp_id&lt;br&gt;relation_type, confidence, graph_relation_id, source_table/source_pk&lt;br&gt;tenant_id, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"> <mxCell id="12" value="question.hw_question_kp_rel&lt;br&gt;PK (question_id, kp_id)&lt;br&gt;FK question_id -&gt; hw_question_item.question_id&lt;br&gt;FK kp_id -&gt; cl_knowledge_point.kp_id&lt;br&gt;relation_type, confidence, graph_relation_id, source_table/source_pk&lt;br&gt;tenant_id, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="780" y="90" width="320" height="130" as="geometry"/> <mxGeometry x="780" y="90" width="320" height="130" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="21" value="外部同步依赖ai&lt;br&gt;ai.tb_ai_knowledge_file&lt;br&gt;ai.tb_ai_knowledge_sync_task&lt;br&gt;ai.tb_ai_graph_entity / ai.tb_ai_graph_relation&lt;br&gt;Target: Milvus + NebulaGraph/Neo4j" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" parent="1" vertex="1"> <mxCell id="21" value="外部同步依赖ai&lt;br&gt;ai.tb_ai_knowledge_file&lt;br&gt;ai.tb_ai_knowledge_sync_task&lt;br&gt;ai.tb_ai_graph_entity / ai.tb_ai_graph_relation&lt;br&gt;Target: Milvus + NebulaGraph/Neo4j" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" parent="1" vertex="1">
<mxGeometry x="40" y="890" width="370" height="150" as="geometry"/> <mxGeometry x="40" y="890" width="370" height="150" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="13" value="question.hw_paper&lt;br&gt;PK paper_id&lt;br&gt;paper_name, subject_code, total_score&lt;br&gt;grading_policy_json&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"> <mxCell id="13" value="question.hw_paper&lt;br&gt;PK paper_id&lt;br&gt;paper_name, paper_type, subject_code, total_score&lt;br&gt;source_file_id, grading_policy_json&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1">
<mxGeometry x="40" y="300" width="290" height="130" as="geometry"/> <mxGeometry x="40" y="300" width="290" height="130" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="14" value="question.hw_paper_question&lt;br&gt;PK (paper_id, question_id)&lt;br&gt;question_order, score, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"> <mxCell id="14" value="question.hw_paper_question&lt;br&gt;PK (paper_id, question_id)&lt;br&gt;question_order, score, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="380" y="320" width="310" height="120" as="geometry"/> <mxGeometry x="380" y="320" width="310" height="120" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="15" value="question.hw_assignment&lt;br&gt;PK assignment_id&lt;br&gt;FK paper_id -&gt; hw_paper.paper_id&lt;br&gt;title, publish_time, deadline, status&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"> <mxCell id="15" value="question.hw_assignment&lt;br&gt;PK assignment_id&lt;br&gt;FK paper_id -&gt; hw_paper.paper_id&lt;br&gt;assignment_mode, title, publish_time, deadline, status&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
<mxGeometry x="740" y="300" width="340" height="140" as="geometry"/> <mxGeometry x="740" y="300" width="340" height="140" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="16" value="question.hw_assignment_target&lt;br&gt;PK target_id&lt;br&gt;FK assignment_id -&gt; hw_assignment.assignment_id&lt;br&gt;target_type, target_ref_id, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"> <mxCell id="16" value="question.hw_assignment_target&lt;br&gt;PK target_id&lt;br&gt;FK assignment_id -&gt; hw_assignment.assignment_id&lt;br&gt;target_type, target_ref_id, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
<mxGeometry x="1130" y="300" width="320" height="130" as="geometry"/> <mxGeometry x="1130" y="300" width="320" height="130" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="17" value="question.hw_submission&lt;br&gt;PK submission_id&lt;br&gt;FK assignment_id -&gt; hw_assignment.assignment_id&lt;br&gt;FK student_id -&gt; tb_sys_user.user_id&lt;br&gt;submit_time, used_seconds, status, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1"> <mxCell id="17" value="question.hw_submission&lt;br&gt;PK submission_id&lt;br&gt;FK assignment_id -&gt; hw_assignment.assignment_id&lt;br&gt;FK student_id -&gt; tb_sys_user.user_id&lt;br&gt;submission_mode, origin_file_id, submit_time&lt;br&gt;used_seconds, status, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
<mxGeometry x="740" y="500" width="360" height="150" as="geometry"/> <mxGeometry x="740" y="500" width="360" height="150" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="18" value="question.hw_submission_answer&lt;br&gt;PK answer_id&lt;br&gt;FK submission_id -&gt; hw_submission.submission_id&lt;br&gt;FK question_id -&gt; hw_question_item.question_id&lt;br&gt;answer_type, answer_payload&lt;br&gt;file_id, file_type, tenant_id&lt;br&gt;UK (submission_id, question_id)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1"> <mxCell id="18" value="question.hw_submission_answer&lt;br&gt;PK answer_id&lt;br&gt;FK submission_id -&gt; hw_submission.submission_id&lt;br&gt;FK question_id -&gt; hw_question_item.question_id&lt;br&gt;answer_type, answer_payload&lt;br&gt;file_id, file_type, tenant_id&lt;br&gt;UK (submission_id, question_id)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
@@ -43,7 +46,8 @@
<mxCell id="19" value="upms.tb_sys_file&lt;br&gt;PK file_id&lt;br&gt;media_type, object_key, file_hash&lt;br&gt;uploaded_by, tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"> <mxCell id="19" value="upms.tb_sys_file&lt;br&gt;PK file_id&lt;br&gt;media_type, object_key, file_hash&lt;br&gt;uploaded_by, tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="1140" y="730" width="320" height="120" as="geometry"/> <mxGeometry x="1140" y="730" width="320" height="120" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="30" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="10" target="11" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="30" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="10" target="20" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="39" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="11" target="20" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="31" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="11" target="12" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="31" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="11" target="12" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="32" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="13" target="14" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="32" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="13" target="14" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="33" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="11" target="14" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="33" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="11" target="14" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>

View File

@@ -28,13 +28,19 @@
<mxCell id="15" value="question.gd_wrong_question&lt;br&gt;PK wrong_question_id&lt;br&gt;student_id, question_id, source_submission_id&lt;br&gt;mastery_status, review_count&lt;br&gt;tenant_id, created_at, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"> <mxCell id="15" value="question.gd_wrong_question&lt;br&gt;PK wrong_question_id&lt;br&gt;student_id, question_id, source_submission_id&lt;br&gt;mastery_status, review_count&lt;br&gt;tenant_id, created_at, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
<mxGeometry x="790" y="290" width="350" height="140" as="geometry"/> <mxGeometry x="790" y="290" width="350" height="140" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="16" value="question.gd_review_plan&lt;br&gt;PK review_plan_id&lt;br&gt;FK wrong_question_id -&gt; gd_wrong_question.wrong_question_id&lt;br&gt;plan_date, plan_stage, plan_status&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"> <mxCell id="16" value="question.gd_review_plan&lt;br&gt;PK review_plan_id&lt;br&gt;FK wrong_question_id -&gt; gd_wrong_question.wrong_question_id&lt;br&gt;plan_date, plan_stage(E1-E10/CUSTOM), plan_status&lt;br&gt;tenant_id, created_at, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
<mxGeometry x="1190" y="290" width="340" height="130" as="geometry"/> <mxGeometry x="1190" y="290" width="340" height="130" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="25" value="question.gd_review_stage_policy&lt;br&gt;PK policy_id&lt;br&gt;stage_code, stage_order, interval_days&lt;br&gt;retry_interval_days, pass_threshold, max_retry_count&lt;br&gt;enabled, tenant_id, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1">
<mxGeometry x="1600" y="280" width="360" height="150" as="geometry"/>
</mxCell>
<mxCell id="26" value="question.gd_review_execution&lt;br&gt;PK execution_id&lt;br&gt;FK review_plan_id -&gt; gd_review_plan.review_plan_id&lt;br&gt;wrong_question_id, student_id, stage_code, result_status&lt;br&gt;score, reviewed_at, retry_count, next_plan_date, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
<mxGeometry x="1600" y="470" width="380" height="170" as="geometry"/>
</mxCell>
<mxCell id="17" value="question.gd_teacher_comment&lt;br&gt;PK comment_id&lt;br&gt;FK answer_grade_id -&gt; gd_answer_grade.answer_grade_id&lt;br&gt;reviewer_id, comment_type, content_ref&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1"> <mxCell id="17" value="question.gd_teacher_comment&lt;br&gt;PK comment_id&lt;br&gt;FK answer_grade_id -&gt; gd_answer_grade.answer_grade_id&lt;br&gt;reviewer_id, comment_type, content_ref&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
<mxGeometry x="420" y="480" width="350" height="130" as="geometry"/> <mxGeometry x="420" y="480" width="350" height="130" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="18" value="question.gd_explanation_submission&lt;br&gt;PK explanation_id&lt;br&gt;student_id, wrong_question_id, source_answer_id&lt;br&gt;audio_file_id, transcript_text, submission_status&lt;br&gt;tenant_id, created_at, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> <mxCell id="18" value="question.gd_explanation_submission&lt;br&gt;PK explanation_id&lt;br&gt;student_id, question_id(必填), wrong_question_id, source_answer_id&lt;br&gt;audio_file_id, transcript_text, submission_status&lt;br&gt;tenant_id, created_at, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
<mxGeometry x="40" y="500" width="340" height="150" as="geometry"/> <mxGeometry x="40" y="500" width="340" height="150" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="19" value="question.gd_explanation_assessment&lt;br&gt;PK assessment_id&lt;br&gt;UK explanation_id -&gt; gd_explanation_submission.explanation_id&lt;br&gt;evaluator_type, total_score, pass_status&lt;br&gt;improvement_suggestion, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> <mxCell id="19" value="question.gd_explanation_assessment&lt;br&gt;PK assessment_id&lt;br&gt;UK explanation_id -&gt; gd_explanation_submission.explanation_id&lt;br&gt;evaluator_type, total_score, pass_status&lt;br&gt;improvement_suggestion, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
@@ -43,7 +49,7 @@
<mxCell id="20" value="question.gd_explanation_dimension_score&lt;br&gt;PK dimension_score_id&lt;br&gt;FK assessment_id -&gt; gd_explanation_assessment.assessment_id&lt;br&gt;dimension_code, dimension_name&lt;br&gt;score, weight, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"> <mxCell id="20" value="question.gd_explanation_dimension_score&lt;br&gt;PK dimension_score_id&lt;br&gt;FK assessment_id -&gt; gd_explanation_assessment.assessment_id&lt;br&gt;dimension_code, dimension_name&lt;br&gt;score, weight, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="1240" y="500" width="340" height="140" as="geometry"/> <mxGeometry x="1240" y="500" width="340" height="140" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="23" value="question.gd_answer_kp_analysis&lt;br&gt;PK analysis_id&lt;br&gt;FK answer_id -&gt; hw_submission_answer.answer_id&lt;br&gt;FK kp_id -&gt; cl_knowledge_point.kp_id&lt;br&gt;correctness, mastery_score, confidence&lt;br&gt;evidence_json, tenant_id, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"> <mxCell id="23" value="question.gd_answer_kp_analysis&lt;br&gt;PK (answer_id, kp_id)&lt;br&gt;FK answer_id -&gt; hw_submission_answer.answer_id&lt;br&gt;FK kp_id -&gt; cl_knowledge_point.kp_id&lt;br&gt;grading_task_id, correctness, mastery_score, confidence&lt;br&gt;evidence_json, tenant_id, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
<mxGeometry x="1240" y="90" width="360" height="160" as="geometry"/> <mxGeometry x="1240" y="90" width="360" height="160" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="21" value="upms.tb_sys_message&lt;br&gt;PK message_id&lt;br&gt;message_type, biz_type, content_object_id&lt;br&gt;web_jump_url, tenant_id, send_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"> <mxCell id="21" value="upms.tb_sys_message&lt;br&gt;PK message_id&lt;br&gt;message_type, biz_type, content_object_id&lt;br&gt;web_jump_url, tenant_id, send_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
@@ -88,6 +94,12 @@
<mxCell id="40" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="11" target="23" edge="1"> <mxCell id="40" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="11" target="23" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="41" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="16" target="26" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="42" value="策略匹配" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;dashed=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="25" target="26" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
</root> </root>
</mxGraphModel> </mxGraphModel>
</diagram> </diagram>

View File

@@ -24,17 +24,17 @@
<mxCell id="36" value="Control&lt;br&gt;SiteMessageService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="640" width="220" height="80" as="geometry"/></mxCell> <mxCell id="36" value="Control&lt;br&gt;SiteMessageService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="640" width="220" height="80" as="geometry"/></mxCell>
<mxCell id="35" value="Port&lt;br&gt;GradingEnginePort&lt;br&gt;(当前规则实现 RuleBasedEngine)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="780" y="80" width="250" height="90" as="geometry"/></mxCell> <mxCell id="35" value="Port&lt;br&gt;GradingEnginePort&lt;br&gt;(当前规则实现 RuleBasedEngine)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="780" y="80" width="250" height="90" as="geometry"/></mxCell>
<mxCell id="40" value="Entity&lt;br&gt;GradingTaskAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="780" y="210" width="230" height="70" as="geometry"/></mxCell> <mxCell id="40" value="Entity&lt;br&gt;GradingTaskAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="780" y="210" width="230" height="70" as="geometry"/></mxCell>
<mxCell id="41" value="Entity&lt;br&gt;ObjectiveScoreAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="780" y="310" width="230" height="70" as="geometry"/></mxCell> <mxCell id="41" value="Entity&lt;br&gt;AnswerGradeAggregate&lt;br&gt;question.gd_answer_grade" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="780" y="310" width="260" height="80" as="geometry"/></mxCell>
<mxCell id="42" value="Entity&lt;br&gt;SubjectiveReviewAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="780" y="410" width="250" height="70" as="geometry"/></mxCell> <mxCell id="42" value="Entity&lt;br&gt;ReviewExecutionAggregate&lt;br&gt;question.gd_review_execution" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="780" y="410" width="270" height="80" as="geometry"/></mxCell>
<mxCell id="43" value="Entity&lt;br&gt;WrongQuestionAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="780" y="510" width="240" height="70" as="geometry"/></mxCell> <mxCell id="43" value="Entity&lt;br&gt;WrongReviewAggregate&lt;br&gt;question.gd_wrong_question / gd_review_plan&lt;br&gt;question.gd_review_stage_policy" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="780" y="510" width="310" height="90" as="geometry"/></mxCell>
<mxCell id="44" value="Entity&lt;br&gt;SiteMessageAggregate&lt;br&gt;message_id, biz_type&lt;br&gt;content_object_id, web_jump_url" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="780" y="640" width="270" height="90" as="geometry"/></mxCell> <mxCell id="44" value="Entity&lt;br&gt;SiteMessageAggregate&lt;br&gt;message_id, biz_type&lt;br&gt;content_object_id, web_jump_url" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="780" y="640" width="270" height="90" as="geometry"/></mxCell>
<mxCell id="50" value="PostgreSQL(question + ai sync task)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="230" width="250" height="90" as="geometry"/></mxCell> <mxCell id="50" value="PostgreSQL(question.gd_* + question.hw_* + ai sync task)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="230" width="280" height="90" as="geometry"/></mxCell>
<mxCell id="51" value="Redis(结果缓存/待复核队列)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="370" width="250" height="90" as="geometry"/></mxCell> <mxCell id="51" value="Redis(结果缓存/待复核队列)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="370" width="250" height="90" as="geometry"/></mxCell>
<mxCell id="52" value="MQ(GradingCompletedEvent)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="510" width="250" height="90" as="geometry"/></mxCell> <mxCell id="52" value="MQ(GradingCompletedEvent)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="510" width="250" height="90" as="geometry"/></mxCell>
<mxCell id="53" value="PostgreSQL(upms.site_message / recipient)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="650" width="250" height="90" as="geometry"/></mxCell> <mxCell id="53" value="PostgreSQL(upms.site_message / recipient)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="650" width="250" height="90" as="geometry"/></mxCell>
<mxCell id="54" value="Redis(未读计数缓存)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="770" width="250" height="90" as="geometry"/></mxCell> <mxCell id="54" value="Redis(未读计数缓存)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="770" width="250" height="90" as="geometry"/></mxCell>
<mxCell id="55" value="Milvus + NebulaGraph/Neo4j" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="890" width="250" height="90" as="geometry"/></mxCell> <mxCell id="55" value="Milvus + NebulaGraph/Neo4j" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="890" width="250" height="90" as="geometry"/></mxCell>
<mxCell id="60" value="设计方法与原因&lt;br&gt;1) 批改调度、判分、复核、错题沉淀分控制器,职责清晰&lt;br&gt;2) 通过 Port 抽象引擎实现后续可无缝接入AI或外部服务&lt;br&gt;3) 主观题待审阅由站内信服务落库含content_object_id和web_jump_url&lt;br&gt;4) 教师通过收件箱点击跳转到对应审阅页,链路可追踪可回执" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1"> <mxCell id="60" value="设计方法与原因&lt;br&gt;1) 批改调度、判分、复核、错题沉淀分控制器,职责清晰&lt;br&gt;2) 通过 Port 抽象引擎实现后续可无缝接入AI或外部服务&lt;br&gt;3) 主观题待审阅由站内信服务落库含content_object_id和web_jump_url&lt;br&gt;4) 教师通过收件箱点击跳转到对应审阅页,链路可追踪可回执&lt;br&gt;5) 艾宾浩斯策略与执行拆分gd_review_stage_policy + gd_review_execution" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="1380" y="230" width="330" height="220" as="geometry"/> <mxGeometry x="1380" y="230" width="330" height="220" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="100" value="查看批改结果" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="10" target="20" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="100" value="查看批改结果" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="10" target="20" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
@@ -46,14 +46,14 @@
<mxCell id="106" value="生成复习计划" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="33" target="34" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="106" value="生成复习计划" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="33" target="34" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="107" value="规则判分调用" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="31" target="35" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="107" value="规则判分调用" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="31" target="35" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="108" value="任务写入" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="30" target="40" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="108" value="任务写入" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="30" target="40" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="109" value="客观分写入" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="31" target="41" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="109" value="批改结果写入" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="31" target="41" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="110" value="复写入" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="32" target="42" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="110" value="复习执行写入" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="32" target="42" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="111" value="错题写入" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="33" target="43" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="111" value="错题写入" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="33" target="43" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="112" value="持久化" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="40" target="50" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="112" value="持久化" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="40" target="50" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="113" value="持久化" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="41" target="50" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="113" value="持久化" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="41" target="50" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="114" value="持久化" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="42" target="50" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="114" value="持久化" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="42" target="50" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="115" value="持久化" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="43" target="50" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="115" value="持久化" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="43" target="50" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="116" value="待复核缓存" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="42" target="51" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="116" value="复习执行缓存" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="42" target="51" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="117" value="完成事件" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="34" target="52" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="117" value="完成事件" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="34" target="52" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="118" value="查看站内信" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="11" target="22" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="118" value="查看站内信" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="11" target="22" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="119" value="待审阅通知触发" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="32" target="36" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell> <mxCell id="119" value="待审阅通知触发" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="32" target="36" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>

View File

@@ -0,0 +1,136 @@
<mxfile host="65bd71144e">
<diagram id="code-arch-real" name="代码架构">
<mxGraphModel dx="1312" dy="773" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="3000" pageHeight="1900" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="K12Study 代码架构(按当前目录)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=24;fontStyle=1;" parent="1" vertex="1">
<mxGeometry x="30" y="20" width="760" height="34" as="geometry"/>
</mxCell>
<mxCell id="3" value="主工程目录frontend / app / backend / init / docker / .devcontainer与仓库当前结构对齐" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=13;fontColor=#666666;" parent="1" vertex="1">
<mxGeometry x="30" y="58" width="1600" height="24" as="geometry"/>
</mxCell>
<mxCell id="10" value="前端工程目录" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#d0d0d0;dashed=1;align=left;verticalAlign=top;spacingLeft=8;spacingTop=6;fontSize=12;fontColor=#666666;" parent="1" vertex="1">
<mxGeometry x="40" y="120" width="700" height="560" as="geometry"/>
</mxCell>
<mxCell id="11" value="frontend/&#xa;src/{api,components,layouts,pages,router,store,styles,types,utils}&#xa;package.json / vite.config.ts" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="80" y="220" width="620" height="150" as="geometry"/>
</mxCell>
<mxCell id="12" value="app/&#xa;src/{api,pages,utils}&#xa;package.json / project.config.json" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="80" y="410" width="620" height="130" as="geometry"/>
</mxCell>
<mxCell id="20" value="后端 Java 多模块目录backend" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#edf4ff;strokeColor=#c0d7f2;dashed=1;align=left;verticalAlign=top;spacingLeft=8;spacingTop=6;fontSize=12;fontColor=#4a6b8a;" parent="1" vertex="1">
<mxGeometry x="780" y="120" width="1250" height="800" as="geometry"/>
</mxCell>
<mxCell id="21" value="backend/pom.xml聚合&#xa;modules: common / apis / ai-client / auth / upms / gateway / boot-dev" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="820" y="170" width="1170" height="110" as="geometry"/>
</mxCell>
<mxCell id="22" value="backend/common/&#xa;common-api, common-core, common-web,&#xa;common-security, common-mybatis, common-redis" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="820" y="340" width="560" height="180" as="geometry"/>
</mxCell>
<mxCell id="23" value="backend/apis/&#xa;api-auth, api-upms, api-ai" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="1430" y="340" width="560" height="150" as="geometry"/>
</mxCell>
<mxCell id="24" value="backend/gateway" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="820" y="580" width="360" height="95" as="geometry"/>
</mxCell>
<mxCell id="25" value="backend/auth" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="1220" y="580" width="360" height="95" as="geometry"/>
</mxCell>
<mxCell id="26" value="backend/upms" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="1610" y="580" width="360" height="95" as="geometry"/>
</mxCell>
<mxCell id="27" value="backend/ai-client&#xa;Java 调用 AI 接口客户端" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="820" y="710" width="540" height="130" as="geometry"/>
</mxCell>
<mxCell id="28" value="backend/boot-dev&#xa;本地聚合启动auth + upms + ai-client" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="1410" y="710" width="560" height="130" as="geometry"/>
</mxCell>
<mxCell id="30" value="Python AI 目录backend/python-ai" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff7e6;strokeColor=#e9cf9a;dashed=1;align=left;verticalAlign=top;spacingLeft=8;spacingTop=6;fontSize=12;fontColor=#8a6d3b;" parent="1" vertex="1">
<mxGeometry x="2080" y="120" width="860" height="460" as="geometry"/>
</mxCell>
<mxCell id="31" value="backend/python-ai/&#xa;app/main.pyFastAPI&#xa;requirements.txt / README.md" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="2140" y="240" width="740" height="220" as="geometry"/>
</mxCell>
<mxCell id="40" value="部署与初始化目录" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f7f7f7;strokeColor=#cfcfcf;dashed=1;align=left;verticalAlign=top;spacingLeft=8;spacingTop=6;fontSize=12;fontColor=#666666;" parent="1" vertex="1">
<mxGeometry x="40" y="980" width="2900" height="700" as="geometry"/>
</mxCell>
<mxCell id="41" value="init/pg/&#xa;achievement, ai, auth, course,&#xa;question, sys, upms" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="120" y="1090" width="560" height="200" as="geometry"/>
</mxCell>
<mxCell id="42" value="docker/dev-common/&#xa;docker-compose.yml&#xa;docker-compose.public-services.yml" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="760" y="1090" width="560" height="200" as="geometry"/>
</mxCell>
<mxCell id="43" value=".devcontainer/&#xa;internal/ / external/" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="1400" y="1090" width="520" height="180" as="geometry"/>
</mxCell>
<mxCell id="44" value="docs/(架构文档与图)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="2000" y="1090" width="860" height="140" as="geometry"/>
</mxCell>
<mxCell id="80" value="API 调用" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="11" target="24" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="81" value="API 调用" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="12" target="24" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="82" value="本地联调" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;dashed=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="11" target="28" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="83" value="本地联调" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;dashed=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="12" target="28" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="84" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="24" target="25" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="85" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="24" target="26" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="86" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="24" target="27" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="87" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="25" target="22" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="88" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="25" target="23" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="89" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="26" target="22" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="90" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="26" target="23" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="91" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="27" target="23" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="92" value="HTTP/RPC" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="27" target="31" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="93" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;dashed=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="28" target="25" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="94" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;dashed=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="28" target="26" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="95" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;dashed=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="28" target="27" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="96" value="SQL 初始化" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" parent="1" source="41" target="25" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="97" value="SQL 初始化" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" parent="1" source="41" target="26" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="98" value="容器编排" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" parent="1" source="42" target="24" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="99" value="容器编排" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;dashed=1;endArrow=block;endFill=1;strokeColor=#666666;" parent="1" source="42" target="28" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="100" value="开发环境定义" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" parent="1" source="43" target="42" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -1,6 +1,6 @@
<mxfile host="65bd71144e"> <mxfile host="65bd71144e">
<diagram id="full-business-flow-v3" name="完整业务流程图"> <diagram id="full-business-flow-v3" name="完整业务流程图">
<mxGraphModel dx="2300" dy="1500" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="5600" pageHeight="2300" math="0" shadow="0"> <mxGraphModel dx="1312" dy="773" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="5600" pageHeight="2300" math="0" shadow="0">
<root> <root>
<mxCell id="0"/> <mxCell id="0"/>
<mxCell id="1" parent="0"/> <mxCell id="1" parent="0"/>
@@ -13,16 +13,16 @@
<mxCell id="91" value="course" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=11;" parent="1" vertex="1"> <mxCell id="91" value="course" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=11;" parent="1" vertex="1">
<mxGeometry x="2550" y="62" width="92" height="28" as="geometry"/> <mxGeometry x="2550" y="62" width="92" height="28" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="92" value="homework" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=11;" parent="1" vertex="1"> <mxCell id="92" value="question.hw_*" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=11;" parent="1" vertex="1">
<mxGeometry x="2650" y="62" width="92" height="28" as="geometry"/> <mxGeometry x="2650" y="62" width="92" height="28" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="93" value="grading" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=11;" parent="1" vertex="1"> <mxCell id="93" value="question.gd_*" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=11;" parent="1" vertex="1">
<mxGeometry x="2750" y="62" width="92" height="28" as="geometry"/> <mxGeometry x="2750" y="62" width="92" height="28" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="94" value="ai" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=11;" parent="1" vertex="1"> <mxCell id="94" value="ai" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=11;" parent="1" vertex="1">
<mxGeometry x="2850" y="62" width="92" height="28" as="geometry"/> <mxGeometry x="2850" y="62" width="92" height="28" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="95" value="recommendation" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=11;" parent="1" vertex="1"> <mxCell id="95" value="question.rc_*" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=11;" parent="1" vertex="1">
<mxGeometry x="2950" y="62" width="120" height="28" as="geometry"/> <mxGeometry x="2950" y="62" width="120" height="28" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="96" value="achievement" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=11;" parent="1" vertex="1"> <mxCell id="96" value="achievement" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=11;" parent="1" vertex="1">
@@ -31,7 +31,7 @@
<mxCell id="97" value="upms/运营" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=11;" parent="1" vertex="1"> <mxCell id="97" value="upms/运营" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=11;" parent="1" vertex="1">
<mxGeometry x="3200" y="62" width="110" height="28" as="geometry"/> <mxGeometry x="3200" y="62" width="110" height="28" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="10" value="教师主线" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=13;fontStyle=1;align=left;spacingLeft=10;" vertex="1" parent="1"> <mxCell id="10" value="教师主线" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=13;fontStyle=1;align=left;spacingLeft=10;" parent="1" vertex="1">
<mxGeometry x="30" y="110" width="5400" height="220" as="geometry"/> <mxGeometry x="30" y="110" width="5400" height="220" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="11" value="学生主线" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=13;fontStyle=1;align=left;spacingLeft=10;" parent="1" vertex="1"> <mxCell id="11" value="学生主线" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=13;fontStyle=1;align=left;spacingLeft=10;" parent="1" vertex="1">
@@ -49,7 +49,7 @@
<mxCell id="30" value="教师创建课程&#xa;course.cl_course" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=12;" parent="1" vertex="1"> <mxCell id="30" value="教师创建课程&#xa;course.cl_course" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="200" y="170" width="220" height="76" as="geometry"/> <mxGeometry x="200" y="170" width="220" height="76" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="31" value="课程绑定知识点&#xa;course.cl_course_knowledge_rel&#xa;course.cl_knowledge_point" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=12;" parent="1" vertex="1"> <mxCell id="31" value="课程章节/节点绑定知识点&#xa;course.cl_course_node / course.cl_node_kp_rel&#xa;course.cl_chapter_kp_rel / course.cl_knowledge_point" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="450" y="160" width="250" height="96" as="geometry"/> <mxGeometry x="450" y="160" width="250" height="96" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="32" value="组卷生成 Paper&#xa;question.hw_paper&#xa;question.hw_paper_question" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" parent="1" vertex="1"> <mxCell id="32" value="组卷生成 Paper&#xa;question.hw_paper&#xa;question.hw_paper_question" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" parent="1" vertex="1">
@@ -61,7 +61,7 @@
<mxCell id="40" value="学生接收作业&#xa;upms.tb_school_class_member&#xa;question.hw_assignment_target" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" parent="1" vertex="1"> <mxCell id="40" value="学生接收作业&#xa;upms.tb_school_class_member&#xa;question.hw_assignment_target" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="1180" y="440" width="250" height="92" as="geometry"/> <mxGeometry x="1180" y="440" width="250" height="92" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="41" value="学生答题并提交(文本/图片/音频)&#xa;question.hw_submission&#xa;question.hw_submission_answer / upms.tb_sys_file" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" parent="1" vertex="1"> <mxCell id="41" value="学生答题并提交(文本/图片/音频,所有题均记录不区分对错&#xa;question.hw_submission&#xa;question.hw_submission_answer / upms.tb_sys_file" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="1460" y="430" width="320" height="110" as="geometry"/> <mxGeometry x="1460" y="430" width="320" height="110" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="50" value="创建批改任务&#xa;question.gd_grading_task" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1"> <mxCell id="50" value="创建批改任务&#xa;question.gd_grading_task" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1">
@@ -85,61 +85,61 @@
<mxCell id="56" value="错题沉淀入错题本&#xa;question.gd_wrong_question" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1"> <mxCell id="56" value="错题沉淀入错题本&#xa;question.gd_wrong_question" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="3190" y="760" width="240" height="76" as="geometry"/> <mxGeometry x="3190" y="760" width="240" height="76" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="57" value="错题状态流转(待学习/复习中/已掌握&#xa;question.gd_wrong_question&#xa;question.gd_review_plan + ai.tb_ai_knowledge_sync_task" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1"> <mxCell id="57" value="错题状态流转(并行触发:艾宾浩斯路线 + 费曼路线&#xa;question.gd_wrong_question&#xa;question.gd_review_plan + question.gd_review_stage_policy + question.gd_review_execution" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="3460" y="744" width="280" height="108" as="geometry"/> <mxGeometry x="3460" y="744" width="280" height="108" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="60" value="AI生成举一反三变式题&#xa;ai.tb_ai_knowledge_sync_task&#xa;recommendation.rc_recommendation_item" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=12;" parent="1" vertex="1"> <mxCell id="60" value="AI生成举一反三变式题&#xa;ai.tb_ai_knowledge_sync_task&#xa;question.rc_recommendation_item" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="3190" y="430" width="280" height="110" as="geometry"/> <mxGeometry x="3190" y="430" width="280" height="110" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="61" value="学生变式题练习&#xa;question.hw_submission&#xa;question.hw_submission_answer" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" parent="1" vertex="1"> <mxCell id="61" value="学生变式/复习答题并提交(全量记录)&#xa;question.hw_submission&#xa;question.hw_submission_answer" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="3500" y="440" width="250" height="92" as="geometry"/> <mxGeometry x="3500" y="440" width="250" height="92" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="62" value="是否掌握?&#xa;question.gd_wrong_question.mastery_status" style="rhombus;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1"> <mxCell id="62" value="是否掌握?&#xa;question.gd_wrong_question.mastery_status" style="rhombus;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="3780" y="428" width="200" height="110" as="geometry"/> <mxGeometry x="3780" y="428" width="200" height="110" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="63" value="生成艾宾浩斯复习计划&#xa;question.gd_review_plan" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1"> <mxCell id="63" value="艾宾浩斯路线:生成复习计划&#xa;question.gd_review_plan / question.gd_review_stage_policy" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="4010" y="432" width="240" height="92" as="geometry"/> <mxGeometry x="4060" y="437" width="240" height="92" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="64" value="到点提醒与复习反馈&#xa;upms.tb_sys_message&#xa;recommendation.rc_learning_loop_event" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1"> <mxCell id="64" value="到点提醒与复习反馈&#xa;upms.tb_sys_message&#xa;question.rc_learning_loop_event + question.gd_review_execution" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="4250" y="900" width="260" height="92" as="geometry"/> <mxGeometry x="4250" y="900" width="260" height="92" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="65" value="推荐召回与排序&#xa;recommendation.rc_recommendation_task&#xa;recommendation.rc_recommendation_item" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1"> <mxCell id="65" value="推荐召回与排序&#xa;question.rc_recommendation_task&#xa;question.rc_recommendation_item" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="4250" y="1000" width="260" height="92" as="geometry"/> <mxGeometry x="4250" y="1000" width="260" height="92" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="66" value="推送微课/专项训练/同类错题&#xa;recommendation.rc_recommendation_item" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1"> <mxCell id="66" value="推送微课/专项训练/同类错题&#xa;question.rc_recommendation_item" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="4250" y="1110" width="260" height="80" as="geometry"/> <mxGeometry x="4250" y="1110" width="260" height="80" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="67" value="学生反馈(曝光/点击/完成)&#xa;recommendation.rc_recommendation_feedback" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1"> <mxCell id="67" value="学生反馈(曝光/点击/完成)&#xa;question.rc_recommendation_feedback" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="4250" y="1210" width="260" height="80" as="geometry"/> <mxGeometry x="4250" y="1210" width="260" height="80" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="68" value="费曼讲解任务触发&#xa;recommendation.rc_learning_loop_event" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1"> <mxCell id="68" value="费曼路线:讲解任务触发&#xa;question.rc_learning_loop_event" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="2920" y="430" width="240" height="92" as="geometry"/> <mxGeometry x="2920" y="430" width="240" height="92" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="69" value="学生语音讲解提交&#xa;question.gd_explanation_submission&#xa;upms.tb_sys_file" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" parent="1" vertex="1"> <mxCell id="69" value="学生语音讲解提交必须绑定题目question_id&#xa;question.gd_explanation_submission&#xa;upms.tb_sys_file" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="2920" y="540" width="240" height="92" as="geometry"/> <mxGeometry x="2920" y="540" width="240" height="92" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="70" value="AI语音评测ASR/表达评分/建议)&#xa;question.gd_explanation_assessment&#xa;question.gd_explanation_dimension_score&#xa;ai.tb_ai_knowledge_sync_task" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=12;" parent="1" vertex="1"> <mxCell id="70" value="AI语音评测ASR/表达评分/建议)&#xa;question.gd_explanation_assessment&#xa;question.gd_explanation_dimension_score&#xa;ai.tb_ai_knowledge_sync_task" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="3190" y="540" width="300" height="110" as="geometry"/> <mxGeometry x="3190" y="540" width="300" height="110" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="71" value="画像与闭环引擎&#xa;recommendation.rc_student_profile_snapshot&#xa;recommendation.rc_student_profile_feature&#xa;recommendation.rc_learning_loop_case / rc_learning_loop_event" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1"> <mxCell id="71" value="画像与闭环引擎&#xa;question.rc_student_profile_snapshot&#xa;question.rc_student_profile_feature&#xa;question.rc_learning_loop_case / question.rc_learning_loop_event" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="3400" y="980" width="330" height="120" as="geometry"/> <mxGeometry x="3400" y="980" width="330" height="120" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="72" value="规则模板/事件字典/阶段映射&#xa;achievement.ac_achievement_event_dict&#xa;achievement.ac_achievement_metric_def&#xa;achievement.ac_achievement_rule_template&#xa;recommendation.rc_loop_stage_achievement_map / rc_loop_metric_map" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=11;" parent="1" vertex="1"> <mxCell id="72" value="规则模板/事件字典/阶段映射&#xa;achievement.ac_achievement_event_dict&#xa;achievement.ac_achievement_metric_def&#xa;achievement.ac_achievement_rule_template&#xa;question.rc_loop_stage_achievement_map / question.rc_loop_metric_map" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=11;" parent="1" vertex="1">
<mxGeometry x="3400" y="1120" width="330" height="140" as="geometry"/> <mxGeometry x="3400" y="1120" width="330" height="140" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="73" value="成就计算与发放(激励数据=成就数据&#xa;achievement.ac_user_achievement&#xa;achievement.ac_user_achievement_progress" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=12;" parent="1" vertex="1"> <mxCell id="73" value="成就计算与发放(每次提交批改后并行触发&#xa;achievement.ac_user_achievement&#xa;achievement.ac_user_achievement_progress" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="3770" y="980" width="310" height="104" as="geometry"/> <mxGeometry x="3770" y="980" width="310" height="104" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="74" value="成长轨迹/指标更新&#xa;achievement.ac_user_achievement_daily_fact&#xa;recommendation.rc_learning_loop_effect_daily" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=12;" parent="1" vertex="1"> <mxCell id="74" value="成长轨迹/指标更新Redis实时统计 + 日事实落库)&#xa;achievement.ac_user_achievement_daily_fact&#xa;question.rc_learning_loop_effect_daily" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="3770" y="1100" width="310" height="104" as="geometry"/> <mxGeometry x="3770" y="1100" width="310" height="104" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="80" value="机构学情与运营看板&#xa;question.gd_score_summary&#xa;recommendation.rc_recommendation_effect_daily&#xa;achievement.ac_user_achievement_daily_fact" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1"> <mxCell id="80" value="机构学情与运营看板&#xa;question.gd_score_summary&#xa;question.rc_recommendation_effect_daily&#xa;achievement.ac_user_achievement_daily_fact" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="2860" y="1390" width="300" height="116" as="geometry"/> <mxGeometry x="2860" y="1390" width="300" height="116" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="81" value="质量监控与异常预警&#xa;question.gd_grading_task&#xa;recommendation.rc_learning_loop_effect_daily" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1"> <mxCell id="81" value="质量监控与异常预警&#xa;question.gd_grading_task&#xa;question.rc_learning_loop_effect_daily" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="3200" y="1400" width="300" height="100" as="geometry"/> <mxGeometry x="3200" y="1400" width="300" height="100" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="82" value="审计追溯与报表导出&#xa;ai.tb_ai_knowledge_file / ai.tb_ai_knowledge_sync_task&#xa;upms.tb_sys_message&#xa;recommendation.rc_learning_loop_event" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1"> <mxCell id="82" value="审计追溯与报表导出&#xa;ai.tb_ai_knowledge_file / ai.tb_ai_knowledge_sync_task&#xa;upms.tb_sys_message&#xa;question.rc_learning_loop_event" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="3540" y="1390" width="320" height="116" as="geometry"/> <mxGeometry x="3540" y="1390" width="320" height="116" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="83" value="结束&#xa;(进入下一轮学习循环)" style="ellipse;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" parent="1" vertex="1"> <mxCell id="83" value="结束&#xa;(进入下一轮学习循环)" style="ellipse;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" parent="1" vertex="1">
@@ -190,7 +190,7 @@
<mxCell id="114" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="56" target="57" edge="1"> <mxCell id="114" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="56" target="57" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="115" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="56" target="60" edge="1"> <mxCell id="115" value="并行A" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="57" target="60" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="116" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="60" target="61" edge="1"> <mxCell id="116" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="60" target="61" edge="1">
@@ -217,7 +217,7 @@
<mxCell id="123" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="66" target="67" edge="1"> <mxCell id="123" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="66" target="67" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="124" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="57" target="68" edge="1"> <mxCell id="124" value="并行B" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="57" target="68" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="125" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="68" target="69" edge="1"> <mxCell id="125" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="68" target="69" edge="1">
@@ -253,6 +253,15 @@
<mxCell id="135" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="82" target="83" edge="1"> <mxCell id="135" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="82" target="83" edge="1">
<mxGeometry relative="1" as="geometry"/> <mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="136" value="每次批改并行触发" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;dashed=1;endArrow=block;endFill=1;" parent="1" source="55" target="73" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="137" value="复习执行触发" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;dashed=1;endArrow=block;endFill=1;" parent="1" source="64" target="73" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="138" value="费曼评估触发" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;dashed=1;endArrow=block;endFill=1;" parent="1" source="70" target="73" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
</root> </root>
</mxGraphModel> </mxGraphModel>
</diagram> </diagram>

View File

@@ -1,129 +1,201 @@
<?xml version='1.0' encoding='utf-8'?> <mxfile host="65bd71144e">
<mxfile host="app.diagrams.net" modified="2026-04-15T07:55:00.000Z" agent="Oz" version="24.7.17">
<diagram id="multi-role-flow-ai" name="数据流图(多角色)"> <diagram id="multi-role-flow-ai" name="数据流图(多角色)">
<mxGraphModel dx="1800" dy="1200" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="2000" pageHeight="1300" math="0" shadow="0"> <mxGraphModel dx="1312" dy="773" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="2800" pageHeight="1800" math="0" shadow="0">
<root> <root>
<mxCell id="0"/> <mxCell id="0"/>
<mxCell id="1" parent="0"/> <mxCell id="1" parent="0"/>
<mxCell id="2" value="多角色数据流图AI逻辑完整版" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=24;fontStyle=1;" vertex="1" parent="1"> <mxCell id="2" value="多角色数据流图AI逻辑完整版" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=24;fontStyle=1;" parent="1" vertex="1">
<mxGeometry x="30" y="20" width="420" height="30" as="geometry"/> <mxGeometry x="30" y="20" width="460" height="30" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="3" value="入口:微信小程序/React 后台主链路Java服务编排 -&gt; Python AI子服务OCR/LLM/ASR -&gt; PostgreSQL/Redis/对象存储/Milvus/NebulaGraph(Neo4j);同步调度支持 REST/JAR" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=13;fontColor=#666666;" vertex="1" parent="1"> <mxCell id="3" value="入口:微信小程序/React 后台主链路Java服务编排 -&gt; Python AI子服务OCR/LLM/ASR -&gt; PostgreSQL/Redis/对象存储/Milvus/NebulaGraph(Neo4j);同步调度支持 REST/JAR" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=13;fontColor=#666666;" parent="1" vertex="1">
<mxGeometry x="30" y="52" width="1250" height="24" as="geometry"/> <mxGeometry x="30" y="52" width="1850" height="24" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="4" value="角色区" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#d0d0d0;dashed=1;align=left;verticalAlign=top;spacingLeft=8;spacingTop=6;fontSize=12;fontColor=#666666;" vertex="1" parent="1">
<mxCell id="10" value="学生" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=15;fontStyle=1;" vertex="1" parent="1"> <mxGeometry x="20" y="120" width="220" height="620" as="geometry"/>
<mxGeometry x="60" y="120" width="150" height="65" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="11" value="教师" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=15;fontStyle=1;" vertex="1" parent="1"> <mxCell id="5" value="业务流程区" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#edf4ff;strokeColor=#c0d7f2;dashed=1;align=left;verticalAlign=top;spacingLeft=8;spacingTop=6;fontSize=12;fontColor=#4a6b8a;" vertex="1" parent="1">
<mxGeometry x="60" y="250" width="150" height="65" as="geometry"/> <mxGeometry x="90" y="100" width="790" height="1070" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="12" value="机构管理员" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=15;fontStyle=1;" vertex="1" parent="1"> <mxCell id="6" value="AI 服务区" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff7e6;strokeColor=#e9cf9a;dashed=1;align=left;verticalAlign=top;spacingLeft=8;spacingTop=6;fontSize=12;fontColor=#8a6d3b;" vertex="1" parent="1">
<mxGeometry x="60" y="380" width="150" height="65" as="geometry"/> <mxGeometry x="860" y="240" width="420" height="860" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="7" value="数据存储区" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f7f7f7;strokeColor=#cfcfcf;dashed=1;align=left;verticalAlign=top;spacingLeft=8;spacingTop=6;fontSize=12;fontColor=#666666;" vertex="1" parent="1">
<mxCell id="20" value="P1 接入与鉴权&#xa;Gateway + Auth + RBAC" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1"> <mxGeometry x="1340" y="90" width="370" height="1480" as="geometry"/>
<mxGeometry x="280" y="90" width="260" height="78" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="21" value="P2 作业/资料管理&#xa;布置、上传、提交、附件引用" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1"> <mxCell id="10" value="学生" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=15;fontStyle=1;" parent="1" vertex="1">
<mxGeometry x="280" y="200" width="260" height="78" as="geometry"/> <mxGeometry x="45" y="150" width="160" height="70" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="22" value="P3 批改编排服务&#xa;触发AI子流程、汇总得分/错因" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1"> <mxCell id="11" value="教师" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=15;fontStyle=1;" parent="1" vertex="1">
<mxGeometry x="280" y="310" width="260" height="78" as="geometry"/> <mxGeometry x="45" y="350" width="160" height="70" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="23" value="P4 错题本与复习计划&#xa;错题沉淀、复习节点评估" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1"> <mxCell id="12" value="机构管理员" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=15;fontStyle=1;" parent="1" vertex="1">
<mxGeometry x="280" y="420" width="260" height="78" as="geometry"/> <mxGeometry x="45" y="550" width="160" height="70" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="24" value="P5 推荐与消息触达&#xa;推荐任务、反馈回流、站内信" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1"> <mxCell id="20" value="P1 接入与鉴权&#xa;Gateway + Auth + RBAC" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="280" y="530" width="260" height="78" as="geometry"/> <mxGeometry x="320" y="130" width="280" height="84" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="25" value="P6 讲解评估与教学干预&#xa;语音讲解评估、教师点评、学情回传" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1"> <mxCell id="21" value="P2 作业/资料管理&#xa;布置、上传、提交、附件引用" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="280" y="640" width="260" height="78" as="geometry"/> <mxGeometry x="320" y="280" width="280" height="84" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="26" value="P7 画像与学习闭环引擎&#xa;画像快照、阶段事件、成就触发映射" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1"> <mxCell id="22" value="P3 批改编排服务&#xa;触发AI子流程、汇总得分/错因" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="280" y="750" width="260" height="78" as="geometry"/> <mxGeometry x="320" y="430" width="280" height="84" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="23" value="P4 错题本与艾宾浩斯复习&#xa;错题沉淀、复习节点评估" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" parent="1" vertex="1">
<mxCell id="40" value="AI-1 OCR解析服务&#xa;图片增强 / 版面解析 / 文本结构化" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1"> <mxGeometry x="120" y="600" width="280" height="84" as="geometry"/>
<mxGeometry x="610" y="220" width="300" height="78" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="41" value="AI-2 LLM批改服务&#xa;客观判分 / 主观评估 / 错因标签" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1"> <mxCell id="24" value="P5 推荐与消息触达&#xa;推荐任务、反馈回流、站内信" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="610" y="320" width="300" height="78" as="geometry"/> <mxGeometry x="120" y="780" width="280" height="84" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="42" value="AI-3 讲解评测服务&#xa;ASR转写 / 表达评分 / 改进建议" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1"> <mxCell id="25" value="P6 费曼讲解评估与教学干预&#xa;语音讲解评估、教师点评、学情回传" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="610" y="640" width="300" height="78" as="geometry"/> <mxGeometry x="540" y="600" width="280" height="84" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="43" value="AI-4 推荐策略服务&#xa;召回 / 排序 / 反馈学习" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1"> <mxCell id="26" value="P7 画像与学习闭环引擎&#xa;画像快照、阶段事件、成就触发映射" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="610" y="540" width="300" height="78" as="geometry"/> <mxGeometry x="350" y="980" width="320" height="88" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="44" value="AI-5 模型与任务管理&#xa;model_config / task_log / retrieval_log&#xa;sync_scheduler_config / sync_dispatch_log" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1"> <mxCell id="40" value="AI-1 OCR解析服务&#xa;图片增强 / 版面解析 / 文本结构化" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="610" y="760" width="300" height="78" as="geometry"/> <mxGeometry x="900" y="280" width="320" height="84" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="41" value="AI-2 LLM批改服务&#xa;客观判分 / 主观评估 / 错因标签" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxCell id="60" value="D1 PostgreSQL 用户/组织/班级库&#xa;upms/auth" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" vertex="1" parent="1"> <mxGeometry x="900" y="430" width="320" height="84" as="geometry"/>
<mxGeometry x="980" y="90" width="280" height="80" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="61" value="D2 PostgreSQL question题目/作业库&#xa;question.hw_*" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" vertex="1" parent="1"> <mxCell id="42" value="AI-3 讲解评测服务&#xa;ASR转写 / 表达评分 / 改进建议" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="980" y="200" width="280" height="80" as="geometry"/> <mxGeometry x="900" y="600" width="320" height="84" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="62" value="D3 PostgreSQL question批改/错题/讲解库&#xa;question.gd_*" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" vertex="1" parent="1"> <mxCell id="43" value="AI-4 推荐策略服务&#xa;召回 / 排序 / 反馈学习" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="980" y="310" width="280" height="80" as="geometry"/> <mxGeometry x="900" y="780" width="320" height="84" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="63" value="D4 PostgreSQL 推荐/画像/闭环库&#xa;recommendation" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" vertex="1" parent="1"> <mxCell id="44" value="AI-5 模型与任务管理&#xa;model_config / task_log / retrieval_log&#xa;sync_scheduler_config / sync_dispatch_log" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="980" y="420" width="280" height="80" as="geometry"/> <mxGeometry x="900" y="980" width="320" height="84" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="64" value="D5 PostgreSQL 成就库&#xa;achievement激励=成就)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" vertex="1" parent="1"> <mxCell id="60" value="D1 PostgreSQL 用户/组织/班级库&#xa;upms/auth" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="980" y="530" width="280" height="80" as="geometry"/> <mxGeometry x="1380" y="120" width="300" height="90" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="65" value="D6 Redis缓存层&#xa;会话/推荐缓存/未读计数" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" vertex="1" parent="1"> <mxCell id="61" value="D2 PostgreSQL question题目/作业库&#xa;question.hw_*" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="980" y="640" width="280" height="80" as="geometry"/> <mxGeometry x="1380" y="260" width="300" height="90" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="66" value="D7 对象存储&#xa;文件/课件/音频/附件" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" vertex="1" parent="1"> <mxCell id="62" value="D3 PostgreSQL question批改/错题/复习库&#xa;question.gd_*含gd_review_stage_policy/gd_review_execution" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="980" y="750" width="280" height="80" as="geometry"/> <mxGeometry x="1380" y="430" width="300" height="90" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="67" value="D8 Milvus + NebulaGraph/Neo4j&#xa;向量检索 / 知识图谱 / 同步任务结果" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" vertex="1" parent="1"> <mxCell id="63" value="D4 PostgreSQL 推荐/画像/闭环库&#xa;question.rc_*" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="980" y="860" width="280" height="80" as="geometry"/> <mxGeometry x="1380" y="600" width="300" height="90" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="68" value="D9 站内信与审计BI&#xa;message / logs / reports" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" vertex="1" parent="1"> <mxCell id="64" value="D5 PostgreSQL 成就库&#xa;achievement激励=成就)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="980" y="970" width="280" height="80" as="geometry"/> <mxGeometry x="1380" y="770" width="300" height="90" as="geometry"/>
</mxCell>
<mxCell id="65" value="D6 Redis缓存层&#xa;会话/推荐缓存/未读计数" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="1380" y="940" width="300" height="90" as="geometry"/>
</mxCell>
<mxCell id="66" value="D7 对象存储&#xa;文件/课件/音频/附件" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="1380" y="1110" width="300" height="90" as="geometry"/>
</mxCell>
<mxCell id="67" value="D8 Milvus + NebulaGraph/Neo4j&#xa;向量检索 / 知识图谱 / 同步任务结果" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="1380" y="1280" width="300" height="90" as="geometry"/>
</mxCell>
<mxCell id="68" value="D9 站内信与审计BI&#xa;message / logs / reports" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="1380" y="1450" width="300" height="90" as="geometry"/>
</mxCell>
<mxCell id="80" value="登录/绑定" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="10" target="20" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="81" value="教学管理" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="11" target="20" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="82" value="运营管控" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="12" target="20" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="83" value="作业/资料请求" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="20" target="21" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="84" value="批改确认触发" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="21" target="22" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="85" value="错题沉淀(艾宾浩斯)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="22" target="23" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="86" value="推荐触达" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="23" target="24" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="87" value="费曼讲解(并行)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="22" target="25" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="88" value="画像闭环" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="25" target="26" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="89" value="OCR请求" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="22" target="40" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="90" value="LLM批改请求" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="22" target="41" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="91" value="语音评测请求" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="25" target="42" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="92" value="推荐排序请求" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="24" target="43" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="93" value="任务记录/模型路由" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="40" target="44" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="94" value="任务记录/模型路由" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="41" target="44" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="95" value="任务记录/模型路由" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="42" target="44" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="96" value="任务记录/模型路由" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="43" target="44" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="100" value="用户/权限" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" parent="1" source="20" target="60" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="101" value="作业/提交" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" parent="1" source="21" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="102" value="批改确认结果/错题/讲解" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" parent="1" source="22" target="62" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="103" value="错题/复习计划" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" parent="1" source="23" target="62" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="104" value="推荐任务/反馈" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" parent="1" source="24" target="63" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="105" value="成就进度/发放" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" parent="1" source="26" target="64" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="106" value="缓存读写" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" parent="1" source="24" target="65" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="107" value="文件/知识资产" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" parent="1" source="40" target="66" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="108" value="检索/图谱" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" parent="1" source="43" target="67" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="109" value="消息/审计报表" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" parent="1" source="24" target="68" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="110" value="AI日志/任务" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" parent="1" source="44" target="68" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="114" value="同步调度REST/JAR" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;dashed=1;endArrow=block;endFill=1;strokeColor=#666666;" parent="1" source="44" target="67" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="115" value="所有批改确认结果(含对/错)→画像更新/闭环事件" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;dashed=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="22" target="26" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="116" value="画像快照/闭环事件更新" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" parent="1" source="26" target="63" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="111" value="结果回传" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="22" target="10" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="112" value="待复核/教学建议" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="25" target="11" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="113" value="质量看板" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="26" target="12" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell> </mxCell>
<mxCell id="80" value="登录/绑定" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" edge="1" parent="1" source="10" target="20"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="81" value="教学管理" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="11" target="20"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="82" value="运营管控" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" edge="1" parent="1" source="12" target="20"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="83" value="作业/资料请求" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="20" target="21"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="84" value="批改触发" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="21" target="22"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="85" value="错题沉淀" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="22" target="23"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="86" value="推荐触达" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="23" target="24"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="87" value="讲解评估" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="24" target="25"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="88" value="画像闭环" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="25" target="26"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="89" value="OCR请求" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="22" target="40"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="90" value="LLM批改请求" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="22" target="41"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="91" value="语音评测请求" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="25" target="42"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="92" value="推荐排序请求" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="24" target="43"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="93" value="任务记录/模型路由" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="40" target="44"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="94" value="任务记录/模型路由" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="41" target="44"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="95" value="任务记录/模型路由" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="42" target="44"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="96" value="任务记录/模型路由" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="43" target="44"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="100" value="用户/权限" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="20" target="60"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="101" value="作业/提交" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="21" target="61"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="102" value="批改/错题/讲解" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="22" target="62"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="103" value="错题/复习计划" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="23" target="62"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="104" value="推荐任务/反馈/画像" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="24" target="63"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="105" value="成就进度/发放" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="26" target="64"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="106" value="缓存读写" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="24" target="65"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="107" value="文件/知识资产" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="40" target="66"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="108" value="检索/图谱" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="43" target="66"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="109" value="消息/审计报表" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="24" target="68"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="110" value="AI日志/任务" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="44" target="68"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="114" value="同步调度REST/JAR" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;dashed=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="44" target="67"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="111" value="结果回传" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" edge="1" parent="1" source="22" target="10"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="112" value="待复核/教学建议" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="25" target="11"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="113" value="质量看板" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" edge="1" parent="1" source="26" target="12"><mxGeometry relative="1" as="geometry"/></mxCell>
</root> </root>
</mxGraphModel> </mxGraphModel>
</diagram> </diagram>

View File

@@ -0,0 +1,205 @@
<mxfile host="65bd71144e">
<diagram id="sys-arch" name="系统架构">
<mxGraphModel dx="1312" dy="773" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1800" pageHeight="1200" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="AI智能学习系统系统架构" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=24;fontStyle=1;" parent="1" vertex="1">
<mxGeometry x="30" y="20" width="420" height="30" as="geometry"/>
</mxCell>
<mxCell id="3" value="技术栈:微信小程序 + React + Java + Python(AI处理供 Java 调用) + Redis + PostgreSQL + Milvus + NebulaGraph/Neo4j" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=13;fontColor=#666666;" parent="1" vertex="1">
<mxGeometry x="30" y="52" width="760" height="22" as="geometry"/>
</mxCell>
<mxCell id="10" value="角色与前端接入层" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
<mxGeometry x="40" y="100" width="240" height="600" as="geometry"/>
</mxCell>
<mxCell id="11" value="学生端&lt;br&gt;微信小程序&lt;br&gt;学习执行 / 作业上传 / 推荐接收" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="70" y="155" width="180" height="90" as="geometry"/>
</mxCell>
<mxCell id="12" value="教师端&lt;br&gt;React 管理后台&lt;br&gt;班级管理 / 作业发布 / 教学干预" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="70" y="345" width="180" height="90" as="geometry"/>
</mxCell>
<mxCell id="13" value="机构端&lt;br&gt;React 管理后台&lt;br&gt;运营管控 / 质控 / 知识库审核" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="70" y="535" width="180" height="90" as="geometry"/>
</mxCell>
<mxCell id="20" value="接入层&lt;br&gt;SLB / Nginx / Java Gateway(BFF) 集群&lt;br&gt;统一鉴权 / Token / RBAC / 限流 / 聚合接口" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="340" y="315" width="230" height="120" as="geometry"/>
</mxCell>
<mxCell id="30" value="Java 分布式业务服务集群" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
<mxGeometry x="640" y="80" width="760" height="420" as="geometry"/>
</mxCell>
<mxCell id="31" value="认证与用户中心&lt;br&gt;登录、绑定、机构 / 班级 / 角色权限" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="690" y="140" width="220" height="80" as="geometry"/>
</mxCell>
<mxCell id="32" value="作业与内容服务&lt;br&gt;作业发布、上传、文件元数据、提交记录" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="940" y="140" width="220" height="80" as="geometry"/>
</mxCell>
<mxCell id="33" value="批改编排与学情服务&lt;br&gt;任务编排、错因分析、讲解评估、学情画像" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="1190" y="140" width="220" height="80" as="geometry"/>
</mxCell>
<mxCell id="34" value="错题 / 复习 / 推荐服务&lt;br&gt;错题本、艾宾浩斯计划、推荐触达" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="690" y="280" width="220" height="80" as="geometry"/>
</mxCell>
<mxCell id="35" value="知识库与机构运营服务&lt;br&gt;知识图谱/向量库管理、运营看板、质量监控、报表导出" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="940" y="280" width="220" height="80" as="geometry"/>
</mxCell>
<mxCell id="36" value="分布式治理能力&lt;br&gt;服务注册发现 / 配置中心 / 链路追踪 / 灰度发布" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="1190" y="280" width="220" height="80" as="geometry"/>
</mxCell>
<mxCell id="40" value="Python AI处理层供 Java 调用)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
<mxGeometry x="1460" y="80" width="300" height="260" as="geometry"/>
</mxCell>
<mxCell id="41" value="Python OCR 服务&lt;br&gt;文档解析 / 图片增强 / 结构化提取" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="1490" y="135" width="240" height="60" as="geometry"/>
</mxCell>
<mxCell id="42" value="Python 批改与生成服务&lt;br&gt;LLM批改 / 错因标签 / 变式题生成" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="1490" y="215" width="240" height="60" as="geometry"/>
</mxCell>
<mxCell id="43" value="Python 语音评测服务&lt;br&gt;ASR / 讲解评分 / 教学建议" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="1490" y="295" width="240" height="60" as="geometry"/>
</mxCell>
<mxCell id="50" value="公共支撑层" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
<mxGeometry x="1460" y="390" width="300" height="210" as="geometry"/>
</mxCell>
<mxCell id="51" value="知识库 / 题库 / 课程资源&lt;br&gt;学科 / 年级 / 知识点内容组织" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="1490" y="445" width="240" height="60" as="geometry"/>
</mxCell>
<mxCell id="52" value="画像与推荐策略&lt;br&gt;召回、排序、效果回流" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="1490" y="525" width="240" height="60" as="geometry"/>
</mxCell>
<mxCell id="60" value="数据层与基础设施" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
<mxGeometry x="550" y="625" width="1200" height="250" as="geometry"/>
</mxCell>
<mxCell id="61" value="PostgreSQL 集群&lt;br&gt;业务主库 + 分库分表 + 分区表" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="620" y="650" width="200" height="85" as="geometry"/>
</mxCell>
<mxCell id="62" value="Redis Cluster&lt;br&gt;缓存、会话、排行榜、热数据" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="860" y="650" width="200" height="85" as="geometry"/>
</mxCell>
<mxCell id="63" value="对象存储&lt;br&gt;图片 / PDF / 音频 / 课件 / 讲解录音" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="1100" y="650" width="200" height="85" as="geometry"/>
</mxCell>
<mxCell id="64" value="MQ / 任务调度&lt;br&gt;异步批改、推荐推送、图谱/向量同步REST/JAR" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="1340" y="650" width="200" height="85" as="geometry"/>
</mxCell>
<mxCell id="65" value="Milvus + NebulaGraph/Neo4j&lt;br&gt;向量检索、知识图谱、混合检索日志" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="1580" y="650" width="160" height="85" as="geometry"/>
</mxCell>
<mxCell id="70" value="外部能力" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
<mxGeometry x="40" y="740" width="500" height="150" as="geometry"/>
</mxCell>
<mxCell id="71" value="微信登录 / 订阅消息 / 短信" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="80" y="795" width="180" height="60" as="geometry"/>
</mxCell>
<mxCell id="72" value="第三方 OCR / LLM / ASR 模型接口" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="300" y="795" width="200" height="60" as="geometry"/>
</mxCell>
<mxCell id="80" value="学习接口" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="11" target="20" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="81" value="教学接口" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="12" target="20" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="82" value="运营接口" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="13" target="20" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="83" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="20" target="31" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="84" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="20" target="32" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="85" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="20" target="33" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="86" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="20" target="34" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="87" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="20" target="35" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="88" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="20" target="36" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="89" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="31" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="90" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="31" target="62" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="91" value="登录 / 通知" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="31" target="71" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="92" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="32" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="93" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="32" target="63" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="94" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="32" target="64" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="95" value="Java RPC / HTTP" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="41" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="96" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="42" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="97" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="43" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="98" value="知识点 / 课程匹配" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="33" target="51" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="99" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="100" value="画像 / 策略" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="34" target="52" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="101" value="复习内容" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="34" target="51" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="102" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="34" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="103" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="34" target="62" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="104" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="35" target="51" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="105" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="35" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="106" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="35" target="65" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="107" value="监控 / 链路" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="36" target="65" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="108" value="模型调用" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="41" target="72" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="109" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="42" target="72" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="110" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="43" target="72" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="111" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="51" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="112" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="51" target="63" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="113" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="52" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="114" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="52" target="62" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -1,450 +0,0 @@
<mxfile host="65bd71144e">
<diagram id="sys-arch" name="系统架构">
<mxGraphModel dx="1267" dy="1104" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1800" pageHeight="1200" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="AI智能学习系统系统架构" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=24;fontStyle=1;" parent="1" vertex="1">
<mxGeometry x="30" y="20" width="420" height="30" as="geometry"/>
</mxCell>
<mxCell id="3" value="技术栈:微信小程序 + React + Java + Python(AI处理供 Java 调用) + Redis + PostgreSQL + Milvus + NebulaGraph/Neo4j" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=13;fontColor=#666666;" parent="1" vertex="1">
<mxGeometry x="30" y="52" width="760" height="22" as="geometry"/>
</mxCell>
<mxCell id="10" value="角色与前端接入层" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
<mxGeometry x="40" y="100" width="240" height="600" as="geometry"/>
</mxCell>
<mxCell id="11" value="学生端&lt;br&gt;微信小程序&lt;br&gt;学习执行 / 作业上传 / 推荐接收" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="70" y="155" width="180" height="90" as="geometry"/>
</mxCell>
<mxCell id="12" value="教师端&lt;br&gt;React 管理后台&lt;br&gt;班级管理 / 作业发布 / 教学干预" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="70" y="345" width="180" height="90" as="geometry"/>
</mxCell>
<mxCell id="13" value="机构端&lt;br&gt;React 管理后台&lt;br&gt;运营管控 / 质控 / 知识库审核" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="70" y="535" width="180" height="90" as="geometry"/>
</mxCell>
<mxCell id="20" value="接入层&lt;br&gt;SLB / Nginx / Java Gateway(BFF) 集群&lt;br&gt;统一鉴权 / Token / RBAC / 限流 / 聚合接口" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="340" y="315" width="230" height="120" as="geometry"/>
</mxCell>
<mxCell id="30" value="Java 分布式业务服务集群" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
<mxGeometry x="640" y="80" width="760" height="420" as="geometry"/>
</mxCell>
<mxCell id="31" value="认证与用户中心&lt;br&gt;登录、绑定、机构 / 班级 / 角色权限" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="690" y="140" width="220" height="80" as="geometry"/>
</mxCell>
<mxCell id="32" value="作业与内容服务&lt;br&gt;作业发布、上传、文件元数据、提交记录" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="940" y="140" width="220" height="80" as="geometry"/>
</mxCell>
<mxCell id="33" value="批改编排与学情服务&lt;br&gt;任务编排、错因分析、讲解评估、学情画像" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="1190" y="140" width="220" height="80" as="geometry"/>
</mxCell>
<mxCell id="34" value="错题 / 复习 / 推荐服务&lt;br&gt;错题本、艾宾浩斯计划、推荐触达" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="690" y="280" width="220" height="80" as="geometry"/>
</mxCell>
<mxCell id="35" value="知识库与机构运营服务&lt;br&gt;知识图谱/向量库管理、运营看板、质量监控、报表导出" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="940" y="280" width="220" height="80" as="geometry"/>
</mxCell>
<mxCell id="36" value="分布式治理能力&lt;br&gt;服务注册发现 / 配置中心 / 链路追踪 / 灰度发布" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="1190" y="280" width="220" height="80" as="geometry"/>
</mxCell>
<mxCell id="40" value="Python AI处理层供 Java 调用)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
<mxGeometry x="1460" y="80" width="300" height="260" as="geometry"/>
</mxCell>
<mxCell id="41" value="Python OCR 服务&lt;br&gt;文档解析 / 图片增强 / 结构化提取" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="1490" y="135" width="240" height="60" as="geometry"/>
</mxCell>
<mxCell id="42" value="Python 批改与生成服务&lt;br&gt;LLM批改 / 错因标签 / 变式题生成" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="1490" y="215" width="240" height="60" as="geometry"/>
</mxCell>
<mxCell id="43" value="Python 语音评测服务&lt;br&gt;ASR / 讲解评分 / 教学建议" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="1490" y="295" width="240" height="60" as="geometry"/>
</mxCell>
<mxCell id="50" value="公共支撑层" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
<mxGeometry x="1460" y="390" width="300" height="210" as="geometry"/>
</mxCell>
<mxCell id="51" value="知识库 / 题库 / 课程资源&lt;br&gt;学科 / 年级 / 知识点内容组织" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="1490" y="445" width="240" height="60" as="geometry"/>
</mxCell>
<mxCell id="52" value="画像与推荐策略&lt;br&gt;召回、排序、效果回流" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="1490" y="525" width="240" height="60" as="geometry"/>
</mxCell>
<mxCell id="60" value="数据层与基础设施" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
<mxGeometry x="550" y="625" width="1200" height="250" as="geometry"/>
</mxCell>
<mxCell id="61" value="PostgreSQL 集群&lt;br&gt;业务主库 + 分库分表 + 分区表" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="620" y="650" width="200" height="85" as="geometry"/>
</mxCell>
<mxCell id="62" value="Redis Cluster&lt;br&gt;缓存、会话、排行榜、热数据" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="860" y="650" width="200" height="85" as="geometry"/>
</mxCell>
<mxCell id="63" value="对象存储&lt;br&gt;图片 / PDF / 音频 / 课件 / 讲解录音" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="1100" y="650" width="200" height="85" as="geometry"/>
</mxCell>
<mxCell id="64" value="MQ / 任务调度&lt;br&gt;异步批改、推荐推送、图谱/向量同步REST/JAR" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="1340" y="650" width="200" height="85" as="geometry"/>
</mxCell>
<mxCell id="65" value="Milvus + NebulaGraph/Neo4j&lt;br&gt;向量检索、知识图谱、混合检索日志" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="1580" y="650" width="160" height="85" as="geometry"/>
</mxCell>
<mxCell id="70" value="外部能力" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
<mxGeometry x="40" y="740" width="500" height="150" as="geometry"/>
</mxCell>
<mxCell id="71" value="微信登录 / 订阅消息 / 短信" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="80" y="795" width="180" height="60" as="geometry"/>
</mxCell>
<mxCell id="72" value="第三方 OCR / LLM / ASR 模型接口" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="300" y="795" width="200" height="60" as="geometry"/>
</mxCell>
<mxCell id="80" value="学习接口" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="11" target="20" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="81" value="教学接口" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="12" target="20" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="82" value="运营接口" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="13" target="20" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="83" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="20" target="31" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="84" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="20" target="32" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="85" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="20" target="33" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="86" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="20" target="34" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="87" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="20" target="35" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="88" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="20" target="36" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="89" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="31" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="90" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="31" target="62" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="91" value="登录 / 通知" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="31" target="71" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="92" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="32" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="93" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="32" target="63" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="94" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="32" target="64" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="95" value="Java RPC / HTTP" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="41" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="96" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="42" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="97" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="43" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="98" value="知识点 / 课程匹配" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="33" target="51" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="99" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="100" value="画像 / 策略" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="34" target="52" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="101" value="复习内容" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="34" target="51" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="102" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="34" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="103" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="34" target="62" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="104" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="35" target="51" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="105" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="35" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="106" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="35" target="65" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="107" value="监控 / 链路" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="36" target="65" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="108" value="模型调用" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="41" target="72" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="109" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="42" target="72" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="110" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="43" target="72" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="111" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="51" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="112" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="51" target="63" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="113" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="52" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="114" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="52" target="62" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
<diagram id="code-arch" name="代码架构">
<mxGraphModel dx="887" dy="773" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1800" pageHeight="1200" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="AI智能学习系统代码架构" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=24;fontStyle=1;" parent="1" vertex="1">
<mxGeometry x="30" y="20" width="420" height="30" as="geometry"/>
</mxCell>
<mxCell id="3" value="前端:微信小程序 + React后端Java 分布式服务AIPython 服务数据Redis + PostgreSQL + Milvus + NebulaGraph/Neo4j" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=13;fontColor=#666666;" parent="1" vertex="1">
<mxGeometry x="30" y="52" width="780" height="22" as="geometry"/>
</mxCell>
<mxCell id="10" value="前端应用层" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
<mxGeometry x="40" y="90" width="1700" height="150" as="geometry"/>
</mxCell>
<mxCell id="11" value="apps/weapp-student&lt;br&gt;Taro / UniApp 风格小程序&lt;br&gt;作业上传、错题本、推荐页" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="90" y="145" width="270" height="70" as="geometry"/>
</mxCell>
<mxCell id="12" value="apps/react-teacher-admin&lt;br&gt;React 教师后台&lt;br&gt;班级管理、作业发布、教学点评" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="420" y="145" width="270" height="70" as="geometry"/>
</mxCell>
<mxCell id="13" value="apps/react-org-admin&lt;br&gt;React 机构后台&lt;br&gt;权限配置、运营看板、质量监控" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="750" y="145" width="270" height="70" as="geometry"/>
</mxCell>
<mxCell id="20" value="Java 接入层" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
<mxGeometry x="40" y="270" width="1700" height="150" as="geometry"/>
</mxCell>
<mxCell id="21" value="gateway/java-bff-cluster&lt;br&gt;REST API、聚合接口、限流、灰度、鉴权" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="90" y="325" width="290" height="70" as="geometry"/>
</mxCell>
<mxCell id="22" value="security/auth-center&lt;br&gt;JWT、RBAC、登录态、审计中间件" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="430" y="325" width="290" height="70" as="geometry"/>
</mxCell>
<mxCell id="23" value="integration/openapi-adapter&lt;br&gt;微信、短信、对象存储、消息回调" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="770" y="325" width="290" height="70" as="geometry"/>
</mxCell>
<mxCell id="24" value="governance/distributed-kit&lt;br&gt;注册发现、配置中心、链路追踪、熔断重试" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="1110" y="325" width="320" height="70" as="geometry"/>
</mxCell>
<mxCell id="30" value="Java 领域服务层" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
<mxGeometry x="40" y="450" width="1700" height="250" as="geometry"/>
</mxCell>
<mxCell id="31" value="service/user-class-center&lt;br&gt;用户、班级、机构、角色权限" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="70" y="530" width="240" height="80" as="geometry"/>
</mxCell>
<mxCell id="32" value="service/homework-content&lt;br&gt;作业、资料、上传、文件元数据" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="340" y="530" width="240" height="80" as="geometry"/>
</mxCell>
<mxCell id="33" value="service/grading-orchestrator&lt;br&gt;批改编排、错因分析、学情画像" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="610" y="530" width="240" height="80" as="geometry"/>
</mxCell>
<mxCell id="34" value="service/wrongbook-review&lt;br&gt;错题本、复习计划、掌握状态" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="880" y="530" width="240" height="80" as="geometry"/>
</mxCell>
<mxCell id="35" value="service/recommendation&lt;br&gt;召回排序、内容推荐、消息触达" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="1150" y="530" width="240" height="80" as="geometry"/>
</mxCell>
<mxCell id="36" value="service/ops-knowledge&lt;br&gt;机构运营、质控、知识库审核、报表" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="1420" y="530" width="260" height="80" as="geometry"/>
</mxCell>
<mxCell id="40" value="Python AI服务层独立部署供 Java 调用)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
<mxGeometry x="40" y="730" width="980" height="180" as="geometry"/>
</mxCell>
<mxCell id="41" value="python/ocr-service&lt;br&gt;文档解析、图片增强、结构化输出" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="80" y="795" width="210" height="60" as="geometry"/>
</mxCell>
<mxCell id="42" value="python/grading-llm-service&lt;br&gt;主观题批改、错因标签、变式题生成" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="320" y="795" width="240" height="60" as="geometry"/>
</mxCell>
<mxCell id="43" value="python/speech-eval-service&lt;br&gt;ASR、讲解评分、反馈建议" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="590" y="795" width="210" height="60" as="geometry"/>
</mxCell>
<mxCell id="44" value="python/feature-worker&lt;br&gt;画像特征、推荐特征、离线计算" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="830" y="795" width="160" height="60" as="geometry"/>
</mxCell>
<mxCell id="50" value="共享库 / 契约层" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
<mxGeometry x="1070" y="730" width="670" height="180" as="geometry"/>
</mxCell>
<mxCell id="51" value="contracts/api-schema&lt;br&gt;DTO、API Schema、OpenAPI" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="1100" y="795" width="140" height="60" as="geometry"/>
</mxCell>
<mxCell id="52" value="contracts/domain-model&lt;br&gt;实体、值对象、枚举、状态机" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="1260" y="795" width="140" height="60" as="geometry"/>
</mxCell>
<mxCell id="53" value="common/toolkit&lt;br&gt;日志、异常、配置、工具组件" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="1420" y="795" width="140" height="60" as="geometry"/>
</mxCell>
<mxCell id="54" value="client/ai-bridge&lt;br&gt;Java 调 Python AI RPC / HTTP SDK" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=14;" parent="1" vertex="1">
<mxGeometry x="1580" y="795" width="130" height="60" as="geometry"/>
</mxCell>
<mxCell id="60" value="数据与基础设施层" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
<mxGeometry x="40" y="940" width="1700" height="190" as="geometry"/>
</mxCell>
<mxCell id="61" value="infra/postgresql-cluster&lt;br&gt;业务主库、分库分表、分区表、读写分离" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="70" y="1010" width="250" height="75" as="geometry"/>
</mxCell>
<mxCell id="62" value="infra/redis-cluster&lt;br&gt;缓存、会话、排行榜、热点数据" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="360" y="1010" width="250" height="75" as="geometry"/>
</mxCell>
<mxCell id="63" value="infra/object-storage&lt;br&gt;图片、PDF、音频、视频、课件" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="650" y="1010" width="250" height="75" as="geometry"/>
</mxCell>
<mxCell id="64" value="infra/mq-scheduler&lt;br&gt;异步批改、推荐任务、定时报表" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="940" y="1010" width="250" height="75" as="geometry"/>
</mxCell>
<mxCell id="65" value="infra/search-bi&lt;br&gt;检索索引、统计聚合、运营分析" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="1230" y="1010" width="250" height="75" as="geometry"/>
</mxCell>
<mxCell id="66" value="infra/open-platform&lt;br&gt;微信、短信、OCR、LLM、ASR" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
<mxGeometry x="1520" y="1010" width="190" height="75" as="geometry"/>
</mxCell>
<mxCell id="80" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="11" target="21" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="81" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="12" target="21" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="82" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="13" target="21" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="83" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="21" target="22" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="84" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="21" target="23" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="85" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="21" target="24" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="86" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="22" target="31" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="87" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="21" target="32" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="88" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" parent="1" source="21" target="33" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="89" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="21" target="34" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="90" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="21" target="35" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="91" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="21" target="36" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="92" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="41" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="93" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="42" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="94" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="43" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="95" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="35" target="44" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="96" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="31" target="51" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="97" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="32" target="52" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="98" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="33" target="54" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="99" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="34" target="52" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="100" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="35" target="51" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="101" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="36" target="53" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="102" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="31" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="103" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="31" target="62" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="104" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="32" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="105" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="32" target="63" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="106" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="32" target="64" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="107" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="108" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="33" target="64" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="109" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="34" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="110" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="34" target="62" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="111" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="35" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="112" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="35" target="62" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="113" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="35" target="64" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="114" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="36" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="115" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="36" target="65" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="116" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="23" target="63" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="117" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="23" target="66" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="118" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="24" target="65" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="119" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="41" target="66" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="120" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="42" target="66" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="121" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="43" target="66" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="122" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="44" target="61" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="123" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="44" target="62" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

81
docs/milestone.md Normal file
View File

@@ -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 缺陷清零。
- 关键指标可观测(错误率、超时率、任务积压)。

View File

@@ -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/`
- 部门当前先按“年级、学科等组织维度”建模,不在首版细化更复杂教学组织规则

View File

@@ -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 与关键输入输出对象。

View File

@@ -4,4 +4,3 @@ CREATE SCHEMA IF NOT EXISTS ai;
CREATE SCHEMA IF NOT EXISTS course; CREATE SCHEMA IF NOT EXISTS course;
CREATE SCHEMA IF NOT EXISTS question; CREATE SCHEMA IF NOT EXISTS question;
CREATE SCHEMA IF NOT EXISTS achievement; CREATE SCHEMA IF NOT EXISTS achievement;
CREATE SCHEMA IF NOT EXISTS recommendation;

View File

@@ -4,7 +4,6 @@
\i /docker-entrypoint-initdb.d/course/10_create_course_tables.sql \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/question/10_create_question_tables.sql
\i /docker-entrypoint-initdb.d/achievement/10_create_achievement_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/sys/sys_area.sql
\i /docker-entrypoint-initdb.d/upms/20_init_upms_seed.sql \i /docker-entrypoint-initdb.d/upms/20_init_upms_seed.sql
\i /docker-entrypoint-initdb.d/auth/20_init_auth_seed.sql \i /docker-entrypoint-initdb.d/auth/20_init_auth_seed.sql

View File

@@ -563,6 +563,13 @@ CREATE TABLE IF NOT EXISTS question.gd_review_plan (
plan_status VARCHAR(32) NOT NULL DEFAULT 'TODO', plan_status VARCHAR(32) NOT NULL DEFAULT 'TODO',
tenant_id VARCHAR(64) NOT NULL, tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, 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 CONSTRAINT fk_gd_review_plan_wrong_question
FOREIGN KEY (wrong_question_id) REFERENCES question.gd_wrong_question(wrong_question_id) 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.review_plan_id IS '复习计划ID';
COMMENT ON COLUMN question.gd_review_plan.wrong_question_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_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.plan_status IS '计划状态';
COMMENT ON COLUMN question.gd_review_plan.tenant_id IS '租户ID'; 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.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; DROP TABLE IF EXISTS question.gd_teacher_comment CASCADE;
CREATE TABLE IF NOT EXISTS question.gd_teacher_comment ( 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 ( CREATE TABLE IF NOT EXISTS question.gd_explanation_submission (
explanation_id VARCHAR(64) PRIMARY KEY, explanation_id VARCHAR(64) PRIMARY KEY,
student_id VARCHAR(64) NOT NULL, student_id VARCHAR(64) NOT NULL,
question_id VARCHAR(64) NOT NULL,
wrong_question_id VARCHAR(64), wrong_question_id VARCHAR(64),
source_submission_id VARCHAR(64), source_submission_id VARCHAR(64),
source_answer_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')), CHECK (submission_status IN ('SUBMITTED', 'EVALUATED', 'REJECTED')),
CONSTRAINT fk_gd_explanation_submission_student CONSTRAINT fk_gd_explanation_submission_student
FOREIGN KEY (student_id) REFERENCES upms.tb_sys_user(user_id), 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 CONSTRAINT fk_gd_explanation_submission_wrong_question
FOREIGN KEY (wrong_question_id) REFERENCES question.gd_wrong_question(wrong_question_id), FOREIGN KEY (wrong_question_id) REFERENCES question.gd_wrong_question(wrong_question_id),
CONSTRAINT fk_gd_explanation_submission_source_submission 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 TABLE question.gd_explanation_submission IS '费曼讲解提交表';
COMMENT ON COLUMN question.gd_explanation_submission.explanation_id IS '讲解提交ID'; 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.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.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_submission_id IS '来源作业提交ID';
COMMENT ON COLUMN question.gd_explanation_submission.source_answer_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); ON question.gd_wrong_question(student_id, mastery_status);
CREATE INDEX IF NOT EXISTS idx_gd_review_plan_wrong_question_date CREATE INDEX IF NOT EXISTS idx_gd_review_plan_wrong_question_date
ON question.gd_review_plan(wrong_question_id, plan_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 CREATE INDEX IF NOT EXISTS idx_gd_teacher_comment_answer_grade
ON question.gd_teacher_comment(answer_grade_id); ON question.gd_teacher_comment(answer_grade_id);
CREATE INDEX IF NOT EXISTS idx_gd_explanation_submission_student CREATE INDEX IF NOT EXISTS idx_gd_explanation_submission_student
ON question.gd_explanation_submission(student_id, created_at DESC); 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 CREATE INDEX IF NOT EXISTS idx_gd_explanation_submission_wrong_question
ON question.gd_explanation_submission(wrong_question_id, created_at DESC); ON question.gd_explanation_submission(wrong_question_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_gd_explanation_assessment_pass_status CREATE INDEX IF NOT EXISTS idx_gd_explanation_assessment_pass_status
ON question.gd_explanation_assessment(pass_status, created_at DESC); ON question.gd_explanation_assessment(pass_status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_gd_explanation_dimension_score_assessment CREATE INDEX IF NOT EXISTS idx_gd_explanation_dimension_score_assessment
ON question.gd_explanation_dimension_score(assessment_id, dimension_code); 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);

View File

@@ -3,3 +3,50 @@
-- 批改与反馈模块初始化数据占位 -- 批改与反馈模块初始化数据占位
-- 按需补充错因标签、统一答案批改结果、复习计划策略等种子数据 -- 按需补充错因标签、统一答案批改结果、复习计划策略等种子数据
-- 艾宾浩斯复习阶段默认策略E1-E61/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;

View File

@@ -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);