feat: 新增课程学习/习题/批改模块ER与对象类数据流图

Co-Authored-By: Oz <oz-agent@warp.dev>
This commit is contained in:
2026-04-14 21:13:29 +08:00
parent 7018c61a00
commit ead8e2edd5
9 changed files with 477 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<mxfile host="app.diagrams.net" modified="2026-04-14T10:41:00.000Z" agent="Oz" version="24.7.17">
<diagram id="ctx-er" name="ER图">
<mxGraphModel dx="1800" dy="1200" 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="00 组织与权限上下文 - ER图" 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="450" height="30" as="geometry"/>
</mxCell>
<mxCell id="3" value="统一隔离键adcode / tenant_id / tenant_path / dept_id / dept_path" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=13;fontColor=#666666;" parent="1" vertex="1">
<mxGeometry x="20" y="52" width="720" height="24" as="geometry"/>
</mxCell>
<mxCell id="10" value="tb_sys_tenant&lt;br&gt;PK tenant_id&lt;br&gt;FK parent_tenant_id -&gt; tb_sys_tenant.tenant_id&lt;br&gt;tenant_name, tenant_type, status&lt;br&gt;adcode, tenant_path, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="40" y="120" width="280" height="140" as="geometry"/>
</mxCell>
<mxCell id="11" value="tb_sys_dept&lt;br&gt;PK dept_id&lt;br&gt;FK parent_dept_id -&gt; tb_sys_dept.dept_id&lt;br&gt;FK tenant_id -&gt; tb_sys_tenant.tenant_id&lt;br&gt;dept_name, dept_type&lt;br&gt;adcode, tenant_path, dept_path, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="360" y="120" width="300" height="150" as="geometry"/>
</mxCell>
<mxCell id="12" value="tb_sys_user&lt;br&gt;PK user_id, UK username&lt;br&gt;FK tenant_id -&gt; tb_sys_tenant.tenant_id&lt;br&gt;FK dept_id -&gt; tb_sys_dept.dept_id&lt;br&gt;display_name, password_hash, status&lt;br&gt;adcode, tenant_path, dept_path, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="700" y="120" width="320" height="150" as="geometry"/>
</mxCell>
<mxCell id="13" value="tb_sys_role&lt;br&gt;PK role_id, UK role_code&lt;br&gt;FK tenant_id -&gt; tb_sys_tenant.tenant_id&lt;br&gt;role_name&lt;br&gt;dept_id, dept_path, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="700" y="320" width="290" height="130" as="geometry"/>
</mxCell>
<mxCell id="14" value="tb_sys_permission&lt;br&gt;PK permission_id, UK permission_code&lt;br&gt;FK tenant_id -&gt; tb_sys_tenant.tenant_id&lt;br&gt;permission_name&lt;br&gt;dept_id, dept_path, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="1030" y="320" width="320" height="130" as="geometry"/>
</mxCell>
<mxCell id="15" value="rel_user_role&lt;br&gt;PK (user_id, role_id)&lt;br&gt;FK user_id -&gt; tb_sys_user.user_id&lt;br&gt;FK role_id -&gt; tb_sys_role.role_id&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="1030" y="120" width="290" height="130" as="geometry"/>
</mxCell>
<mxCell id="16" value="rel_role_permission&lt;br&gt;PK (role_id, permission_id)&lt;br&gt;FK role_id -&gt; tb_sys_role.role_id&lt;br&gt;FK permission_id -&gt; tb_sys_permission.permission_id&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="1360" y="120" width="320" height="130" as="geometry"/>
</mxCell>
<mxCell id="17" value="tb_auth_refresh_token&lt;br&gt;PK token_id&lt;br&gt;FK user_id -&gt; tb_sys_user.user_id&lt;br&gt;refresh_token, expire_at, revoked&lt;br&gt;tenant_id, dept_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="40" y="330" width="300" height="130" as="geometry"/>
</mxCell>
<mxCell id="18" value="tb_auth_login_audit&lt;br&gt;PK audit_id&lt;br&gt;FK user_id -&gt; tb_sys_user.user_id&lt;br&gt;username, login_ip, login_status&lt;br&gt;tenant_id, dept_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="360" y="330" width="300" height="130" as="geometry"/>
</mxCell>
<mxCell id="19" value="设计方法与原因&lt;br&gt;1) DDD上下文先行权限域作为业务域前置依赖&lt;br&gt;2) RBAC最小闭环用户-角色-权限拆分降低耦合&lt;br&gt;3) 多租户统一隔离键:保证后续业务表可水平扩展&lt;br&gt;4) 认证审计分离:令牌高频写与审计查询分流" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="1360" y="320" width="320" height="170" as="geometry"/>
</mxCell>
<mxCell id="100" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="11" target="10" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="101" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="12" target="10" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="102" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="12" target="11" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="103" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="13" target="10" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="104" value="M:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="15" target="12" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="105" value="M:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="15" target="13" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="106" value="M:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="16" target="13" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="107" value="M:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="16" target="14" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="108" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="17" target="12" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="109" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="18" target="12" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<mxfile host="app.diagrams.net" modified="2026-04-14T10:41:20.000Z" agent="Oz" version="24.7.17">
<diagram id="ctx-dfd" 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="1800" pageHeight="1200" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="00 组织与权限上下文 - 对象类数据流图" 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"/>
</mxCell>
<mxCell id="10" value="Actor&lt;br&gt;学生" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
<mxGeometry x="40" y="120" width="120" height="70" as="geometry"/>
</mxCell>
<mxCell id="11" value="Actor&lt;br&gt;教师" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
<mxGeometry x="40" y="230" width="120" height="70" as="geometry"/>
</mxCell>
<mxCell id="12" value="Actor&lt;br&gt;机构管理员" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
<mxGeometry x="40" y="340" width="120" height="70" as="geometry"/>
</mxCell>
<mxCell id="20" value="Boundary&lt;br&gt;MiniApp/Web UI" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="230" y="170" width="190" height="90" as="geometry"/>
</mxCell>
<mxCell id="21" value="Boundary&lt;br&gt;GatewayAuthBoundary" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="230" y="300" width="190" height="90" as="geometry"/>
</mxCell>
<mxCell id="30" value="Control&lt;br&gt;AuthController/AuthService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1">
<mxGeometry x="500" y="120" width="240" height="90" as="geometry"/>
</mxCell>
<mxCell id="31" value="Control&lt;br&gt;UpmsController/UpmsQueryService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1">
<mxGeometry x="500" y="240" width="240" height="90" as="geometry"/>
</mxCell>
<mxCell id="32" value="Control&lt;br&gt;RbacPolicyService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1">
<mxGeometry x="500" y="360" width="240" height="90" as="geometry"/>
</mxCell>
<mxCell id="40" value="Entity&lt;br&gt;SysUserAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
<mxGeometry x="820" y="110" width="200" height="70" as="geometry"/>
</mxCell>
<mxCell id="41" value="Entity&lt;br&gt;TenantDeptAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
<mxGeometry x="820" y="210" width="200" height="70" as="geometry"/>
</mxCell>
<mxCell id="42" value="Entity&lt;br&gt;RolePermissionAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
<mxGeometry x="820" y="310" width="200" height="70" as="geometry"/>
</mxCell>
<mxCell id="43" value="Entity&lt;br&gt;AuthTokenStore" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
<mxGeometry x="820" y="410" width="200" height="70" as="geometry"/>
</mxCell>
<mxCell id="50" value="PostgreSQL(upms/auth)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="1090" y="190" width="220" height="80" as="geometry"/>
</mxCell>
<mxCell id="51" value="Redis(Session/Token)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="1090" y="320" width="220" height="80" as="geometry"/>
</mxCell>
<mxCell id="60" value="设计方法与原因&lt;br&gt;1) BCE分层明确交互职责&lt;br&gt;2) 鉴权与查询控制器分离,便于独立扩展&lt;br&gt;3) 权限判定集中到Policy服务降低重复逻辑&lt;br&gt;4) Token热数据落Redis用户组织主数据落PG" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="1360" y="190" width="360" height="180" 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="102" value="高权限请求" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="12" target="21" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="103" value="Token校验" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="21" target="30" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="104" value="当前用户/菜单查询" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="20" target="31" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="105" value="权限判定" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="31" target="32" 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="30" target="40" 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="41" 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="32" target="42" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="109" value="Token签发/撤销" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="30" target="43" 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="40" target="50" 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="41" 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="42" 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="43" target="51" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<mxfile host="app.diagrams.net" modified="2026-04-14T10:42:00.000Z" agent="Oz" version="24.7.17">
<diagram id="course-er" name="ER图">
<mxGraphModel dx="1800" dy="1200" 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="01 课程学习 - ER图" 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="360" height="30" as="geometry"/>
</mxCell>
<mxCell id="3" value="课程主数据 + 学习过程数据分层;所有业务主表均携带 tenant_id/adcode/dept_path" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=13;fontColor=#666666;" parent="1" vertex="1">
<mxGeometry x="20" y="52" width="860" height="24" as="geometry"/>
</mxCell>
<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="40" y="120" width="250" height="130" 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">
<mxGeometry x="330" y="120" width="250" height="130" as="geometry"/>
</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">
<mxGeometry x="620" y="120" width="270" height="130" as="geometry"/>
</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">
<mxGeometry x="930" y="120" width="260" height="120" as="geometry"/>
</mxCell>
<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="1230" y="120" width="280" height="130" as="geometry"/>
</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">
<mxGeometry x="620" y="290" width="290" height="130" as="geometry"/>
</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">
<mxGeometry x="40" y="300" width="230" height="110" as="geometry"/>
</mxCell>
<mxCell id="17" value="cl_course_tag_rel&lt;br&gt;PK (course_id, tag_id)&lt;br&gt;FK course_id -&gt; cl_course.course_id&lt;br&gt;FK tag_id -&gt; cl_course_tag.tag_id&lt;br&gt;tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
<mxGeometry x="300" y="300" width="270" height="120" as="geometry"/>
</mxCell>
<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="930" y="290" width="300" height="140" as="geometry"/>
</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">
<mxGeometry x="1260" y="300" width="320" height="140" as="geometry"/>
</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">
<mxGeometry x="930" y="460" width="300" height="130" as="geometry"/>
</mxCell>
<mxCell id="21" value="设计方法与原因&lt;br&gt;1) 聚合根Course / LearningSession&lt;br&gt;2) 状态机Session状态驱动进度写入&lt;br&gt;3) 标签与知识点解耦,支持多维检索&lt;br&gt;4) 事件明细单表追加,兼顾审计与回放" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
<mxGeometry x="40" y="470" width="840" height="120" as="geometry"/>
</mxCell>
<mxCell id="100" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="11" target="10" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="101" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="12" target="11" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="102" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="15" target="12" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="103" value="M:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="14" target="10" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="104" value="M:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="14" target="13" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="105" value="M:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="17" target="10" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="106" value="M:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="17" target="16" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="107" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="18" target="10" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="108" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="19" target="18" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="109" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="19" target="12" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="110" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="20" target="18" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<mxfile host="app.diagrams.net" modified="2026-04-14T10:42:20.000Z" agent="Oz" version="24.7.17">
<diagram id="course-dfd" 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="1800" pageHeight="1200" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="01 课程学习 - 对象类数据流图" 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="460" height="30" as="geometry"/>
</mxCell>
<mxCell id="10" value="Actor&lt;br&gt;学生" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"><mxGeometry x="40" y="130" width="120" height="70" as="geometry"/></mxCell>
<mxCell id="11" value="Actor&lt;br&gt;教师" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"><mxGeometry x="40" y="240" width="120" height="70" as="geometry"/></mxCell>
<mxCell id="12" value="Actor&lt;br&gt;机构管理员" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1"><mxGeometry x="40" y="350" width="120" height="70" as="geometry"/></mxCell>
<mxCell id="20" value="Boundary&lt;br&gt;CourseAPIBoundary" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="230" y="180" width="190" height="90" as="geometry"/></mxCell>
<mxCell id="21" value="Boundary&lt;br&gt;LearningProgressBoundary" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="230" y="320" width="190" height="90" as="geometry"/></mxCell>
<mxCell id="30" value="Control&lt;br&gt;CourseCatalogService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="500" y="120" width="230" height="90" as="geometry"/></mxCell>
<mxCell id="31" value="Control&lt;br&gt;LearningSessionService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="500" y="240" width="230" height="90" as="geometry"/></mxCell>
<mxCell id="32" value="Control&lt;br&gt;ProgressTrackingService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="500" y="360" width="230" height="90" as="geometry"/></mxCell>
<mxCell id="40" value="Entity&lt;br&gt;CourseAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="800" y="110" width="200" height="70" as="geometry"/></mxCell>
<mxCell id="41" value="Entity&lt;br&gt;KnowledgeIndexAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="800" y="210" width="220" height="70" as="geometry"/></mxCell>
<mxCell id="42" value="Entity&lt;br&gt;LearningSessionAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="800" y="310" width="230" height="70" as="geometry"/></mxCell>
<mxCell id="43" value="Entity&lt;br&gt;LearningProgressAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="800" y="410" width="240" height="70" as="geometry"/></mxCell>
<mxCell id="50" value="PostgreSQL(course/learning)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="170" width="240" 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="320" width="240" height="90" as="geometry"/></mxCell>
<mxCell id="60" value="设计方法与原因&lt;br&gt;1) CQRS课程目录读取与学习进度写入分离&lt;br&gt;2) 会话聚合驱动事件化进度更新&lt;br&gt;3) 热点进度走Redis减轻主库读压&lt;br&gt;4) 课程实体与知识点索引分离,支持后续多策略检索" 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"/>
</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="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="104" value="学习会话创建/结束" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="10" target="21" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="105" value="会话命令" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="21" target="31" 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="21" target="32" 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="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="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="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="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="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="42" 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="43" 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="43" target="51" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<mxfile host="app.diagrams.net" modified="2026-04-14T10:43:00.000Z" agent="Oz" version="24.7.17">
<diagram id="homework-er" name="ER图">
<mxGraphModel dx="1800" dy="1200" 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="02 习题与作业 - ER图" 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="400" height="30" as="geometry"/>
</mxCell>
<mxCell id="10" value="hw_question_bank&lt;br&gt;PK bank_id&lt;br&gt;bank_name, subject_code, grade_code&lt;br&gt;status, tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"><mxGeometry x="40" y="110" width="240" height="120" as="geometry"/></mxCell>
<mxCell id="11" value="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, difficulty&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"><mxGeometry x="310" y="110" width="270" height="130" as="geometry"/></mxCell>
<mxCell id="12" value="hw_question_version&lt;br&gt;PK version_id&lt;br&gt;FK question_id -&gt; hw_question_item.question_id&lt;br&gt;version_no, answer_key, analysis&lt;br&gt;is_current, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"><mxGeometry x="610" y="110" width="280" height="130" as="geometry"/></mxCell>
<mxCell id="13" value="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;weight, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="920" y="110" width="280" height="130" as="geometry"/></mxCell>
<mxCell id="14" value="hw_paper&lt;br&gt;PK paper_id&lt;br&gt;paper_name, subject_code, total_score&lt;br&gt;tenant_id, created_by, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="40" y="290" width="240" height="120" as="geometry"/></mxCell>
<mxCell id="15" value="hw_paper_question&lt;br&gt;PK (paper_id, question_id)&lt;br&gt;FK paper_id -&gt; hw_paper.paper_id&lt;br&gt;FK question_id -&gt; hw_question_item.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="310" y="290" width="300" height="130" as="geometry"/></mxCell>
<mxCell id="16" value="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&lt;br&gt;status(DRAFT/PUBLISHED/CLOSED)&lt;br&gt;tenant_id, created_by" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"><mxGeometry x="640" y="290" width="300" height="150" as="geometry"/></mxCell>
<mxCell id="17" value="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(class/student)&lt;br&gt;target_ref_id, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"><mxGeometry x="970" y="290" width="280" height="130" as="geometry"/></mxCell>
<mxCell id="18" value="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&lt;br&gt;status(SUBMITTED/RESUBMITTED)&lt;br&gt;tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1"><mxGeometry x="1280" y="290" width="330" height="150" as="geometry"/></mxCell>
<mxCell id="19" value="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_content, answer_type, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1"><mxGeometry x="1280" y="470" width="330" height="130" as="geometry"/></mxCell>
<mxCell id="20" value="hw_submission_attachment&lt;br&gt;PK attachment_id&lt;br&gt;FK submission_id -&gt; hw_submission.submission_id&lt;br&gt;file_type, object_key, file_hash&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1"><mxGeometry x="920" y="470" width="330" height="130" as="geometry"/></mxCell>
<mxCell id="21" value="设计方法与原因&lt;br&gt;1) 题目版本化:保障历史作业可追溯&lt;br&gt;2) 试卷与作业分离:一份试卷可多次发布&lt;br&gt;3) 发布对象独立表:支持按班级/学员精细投放&lt;br&gt;4) 提交主表+答案明细:便于后续批改并行处理" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
<mxGeometry x="40" y="470" width="840" height="130" as="geometry"/>
</mxCell>
<mxCell id="100" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="11" target="10" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="101" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="12" target="11" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="102" value="M:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="13" target="11" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="103" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="15" target="14" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="104" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="15" target="11" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="105" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="16" target="14" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="106" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="17" target="16" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="107" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="18" target="16" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="108" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="19" target="18" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="109" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="19" target="11" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="110" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="20" target="18" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<mxfile host="app.diagrams.net" modified="2026-04-14T10:43:20.000Z" agent="Oz" version="24.7.17">
<diagram id="homework-dfd" 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="1800" pageHeight="1200" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="02 习题与作业 - 对象类数据流图" 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="500" height="30" as="geometry"/>
</mxCell>
<mxCell id="10" value="Actor&lt;br&gt;教师" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"><mxGeometry x="40" y="150" width="120" height="70" as="geometry"/></mxCell>
<mxCell id="11" value="Actor&lt;br&gt;学生" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"><mxGeometry x="40" y="300" width="120" height="70" as="geometry"/></mxCell>
<mxCell id="20" value="Boundary&lt;br&gt;TeacherHomeworkBoundary" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="230" y="130" width="220" height="90" as="geometry"/></mxCell>
<mxCell id="21" value="Boundary&lt;br&gt;StudentHomeworkBoundary" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="230" y="280" width="220" height="90" as="geometry"/></mxCell>
<mxCell id="30" value="Control&lt;br&gt;QuestionBankService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="90" width="220" height="80" as="geometry"/></mxCell>
<mxCell id="31" value="Control&lt;br&gt;AssignmentPublishService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="200" width="220" height="80" as="geometry"/></mxCell>
<mxCell id="32" value="Control&lt;br&gt;HomeworkQueryService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="310" width="220" height="80" as="geometry"/></mxCell>
<mxCell id="33" value="Control&lt;br&gt;SubmissionService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="420" width="220" height="80" as="geometry"/></mxCell>
<mxCell id="40" value="Entity&lt;br&gt;QuestionBankAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="810" y="90" width="220" height="70" as="geometry"/></mxCell>
<mxCell id="41" value="Entity&lt;br&gt;AssignmentAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="810" y="200" width="220" height="70" as="geometry"/></mxCell>
<mxCell id="42" value="Entity&lt;br&gt;AssignmentTargetAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="810" y="300" width="240" height="70" as="geometry"/></mxCell>
<mxCell id="43" value="Entity&lt;br&gt;SubmissionAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="810" y="410" width="220" height="70" as="geometry"/></mxCell>
<mxCell id="50" value="PostgreSQL(question/homework)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="170" width="250" height="90" as="geometry"/></mxCell>
<mxCell id="51" value="ObjectStorage(附件/图片/PDF)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="320" width="250" height="90" as="geometry"/></mxCell>
<mxCell id="52" value="MQ(SubmissionCreatedEvent)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="450" width="250" height="90" as="geometry"/></mxCell>
<mxCell id="60" value="设计方法与原因&lt;br&gt;1) 发布链路与作答链路拆分,降低接口复杂度&lt;br&gt;2) 提交成功后发事件,解耦后续批改流程&lt;br&gt;3) 附件文件与答案结构分存,便于扩展多题型&lt;br&gt;4) 题库聚合与作业聚合分离,支持独立演进" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
<mxGeometry x="1380" y="200" width="330" height="190" 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="21" 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="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="31" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="104" value="作业查询" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="21" target="32" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="105" value="提交命令" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="21" target="33" 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="30" target="40" 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="41" 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="31" target="42" 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="33" target="43" 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="40" target="50" 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="41" 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="43" 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="43" target="51" 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="43" target="52" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<mxfile host="app.diagrams.net" modified="2026-04-14T10:44:00.000Z" agent="Oz" version="24.7.17">
<diagram id="grading-er" name="ER图">
<mxGraphModel dx="1800" dy="1200" 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="03 批改与反馈 - ER图不含AI实现细节" 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="560" height="30" as="geometry"/>
</mxCell>
<mxCell id="10" value="gd_grading_task&lt;br&gt;PK grading_task_id&lt;br&gt;FK submission_id -&gt; hw_submission.submission_id&lt;br&gt;status(PENDING/RUNNING/WAIT_REVIEW/DONE)&lt;br&gt;trigger_source, tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"><mxGeometry x="40" y="120" width="330" height="140" as="geometry"/></mxCell>
<mxCell id="11" value="gd_grading_rule_set&lt;br&gt;PK rule_set_id&lt;br&gt;rule_version, subject_code, objective_policy&lt;br&gt;subjective_policy(manual_first)&lt;br&gt;tenant_id, enabled" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"><mxGeometry x="400" y="120" width="320" height="130" as="geometry"/></mxCell>
<mxCell id="12" value="gd_objective_score&lt;br&gt;PK objective_score_id&lt;br&gt;FK grading_task_id -&gt; gd_grading_task.grading_task_id&lt;br&gt;FK answer_id -&gt; hw_submission_answer.answer_id&lt;br&gt;score, matched_rule, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"><mxGeometry x="750" y="120" width="340" height="140" as="geometry"/></mxCell>
<mxCell id="13" value="gd_subjective_review&lt;br&gt;PK review_id&lt;br&gt;FK grading_task_id -&gt; gd_grading_task.grading_task_id&lt;br&gt;FK answer_id -&gt; hw_submission_answer.answer_id&lt;br&gt;reviewer_id, review_score, status, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1"><mxGeometry x="1120" y="120" width="350" height="140" as="geometry"/></mxCell>
<mxCell id="14" value="gd_score_summary&lt;br&gt;PK summary_id&lt;br&gt;FK grading_task_id -&gt; gd_grading_task.grading_task_id&lt;br&gt;total_score, grade_level(A/B/C)&lt;br&gt;surpass_ratio, used_seconds, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="40" y="300" width="330" height="140" as="geometry"/></mxCell>
<mxCell id="15" value="gd_error_tag&lt;br&gt;PK error_tag_id&lt;br&gt;tag_code, tag_name&lt;br&gt;category(审题/计算/概念)&lt;br&gt;tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="400" y="300" width="300" height="130" as="geometry"/></mxCell>
<mxCell id="16" value="gd_answer_error_rel&lt;br&gt;PK (answer_id, error_tag_id)&lt;br&gt;FK answer_id -&gt; hw_submission_answer.answer_id&lt;br&gt;FK error_tag_id -&gt; gd_error_tag.error_tag_id&lt;br&gt;confidence, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="730" y="300" width="330" height="130" as="geometry"/></mxCell>
<mxCell id="17" value="gd_wrong_question&lt;br&gt;PK wrong_question_id&lt;br&gt;FK student_id -&gt; tb_sys_user.user_id&lt;br&gt;FK question_id -&gt; hw_question_item.question_id&lt;br&gt;source_submission_id, mastery_status&lt;br&gt;review_count, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"><mxGeometry x="1090" y="300" width="350" height="150" as="geometry"/></mxCell>
<mxCell id="18" value="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/E2/E3)&lt;br&gt;status, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"><mxGeometry x="40" y="490" width="330" height="130" as="geometry"/></mxCell>
<mxCell id="19" value="gd_teacher_comment&lt;br&gt;PK comment_id&lt;br&gt;FK review_id -&gt; gd_subjective_review.review_id&lt;br&gt;comment_type(text/voice), content_ref&lt;br&gt;reviewer_id, tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1"><mxGeometry x="400" y="490" width="340" height="130" as="geometry"/></mxCell>
<mxCell id="20" value="设计方法与原因&lt;br&gt;1) 批改任务聚合承接状态机,保证流程一致性&lt;br&gt;2) 客观分与主观复核拆表,支持并行与补录&lt;br&gt;3) 错因标签与错题沉淀解耦,便于策略演进&lt;br&gt;4) 仅保留 GradingEnginePort 抽象不绑定AI实现" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
<mxGeometry x="780" y="500" width="660" height="130" as="geometry"/>
</mxCell>
<mxCell id="100" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="10" target="11" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="101" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="12" target="10" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="102" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="13" target="10" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="103" value="1:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="14" target="10" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="104" value="M:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="16" target="15" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="105" value="衍生" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="17" target="14" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="106" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="18" target="17" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="107" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="19" target="13" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<mxfile host="app.diagrams.net" modified="2026-04-14T10:44:20.000Z" agent="Oz" version="24.7.17">
<diagram id="grading-dfd" 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="1800" pageHeight="1200" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="03 批改与反馈 - 对象类数据流图(规则批改 + 教师复核)" 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="700" height="30" as="geometry"/>
</mxCell>
<mxCell id="10" value="Actor&lt;br&gt;学生" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"><mxGeometry x="40" y="150" width="120" height="70" as="geometry"/></mxCell>
<mxCell id="11" value="Actor&lt;br&gt;教师" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"><mxGeometry x="40" y="300" width="120" height="70" as="geometry"/></mxCell>
<mxCell id="20" value="Boundary&lt;br&gt;StudentResultBoundary" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="230" y="140" width="220" height="90" as="geometry"/></mxCell>
<mxCell id="21" value="Boundary&lt;br&gt;TeacherReviewBoundary" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="230" y="290" width="220" height="90" as="geometry"/></mxCell>
<mxCell id="30" value="Control&lt;br&gt;GradingDispatcher" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="80" width="220" height="80" as="geometry"/></mxCell>
<mxCell id="31" value="Control&lt;br&gt;ObjectiveScoringService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="190" width="220" height="80" as="geometry"/></mxCell>
<mxCell id="32" value="Control&lt;br&gt;ReviewWorkflowService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="300" width="220" height="80" as="geometry"/></mxCell>
<mxCell id="33" value="Control&lt;br&gt;WrongBookService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="410" width="220" height="80" as="geometry"/></mxCell>
<mxCell id="34" value="Control&lt;br&gt;ReviewPlannerService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="520" 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="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="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="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="50" value="PostgreSQL(grading/wrongbook)" 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="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="60" value="设计方法与原因&lt;br&gt;1) 批改调度、判分、复核、错题沉淀分控制器,职责清晰&lt;br&gt;2) 通过 Port 抽象引擎实现后续可无缝接入AI或外部服务&lt;br&gt;3) 结果完成后事件化通知,支撑复习计划与消息触达&lt;br&gt;4) 主流程先保障规则可用,再逐步增强智能能力" 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"/>
</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="21" 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="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="30" target="31" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="104" value="复核命令" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="21" target="32" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
<mxCell id="105" value="错题沉淀" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="32" target="33" 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="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="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="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="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="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>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -0,0 +1,44 @@
# 课程学习-习题-批改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 与关键输入输出对象。