Skip to content

💻 开发者指南

约束

以下路径是硬编码的,目录结构不能动:

  • qx-dashboard-ts/vite.config.tsoutDir: ../../qc-pocket-space/frontend/dashboard
  • backend/main.py_DATA_ROOT: ../../data/
  • qx-launcher/app/config.py → 向上找兄弟 qc-pocket-space/
  • tests/seed_dev_data.pyPROJECT_ROOT 向上 3 级

数据组织概述

所有数据都在 data/ 目录下,结构如下:

data/
├── qingcheng.db              ← SQLite 数据库(用户账号 + 回收站)
├── users/                    ← 用户文件数据
│   ├── admins/{id}/          ← 管理员
│   ├── teachers/{id}/        ← 教师
│   └── students/{id}/        ← 学生
├── courses/                  ← 课件素材
├── trash/                    ← 回收站
└── share/                    ← 共享文件

核心设计原则:数据库只管理登录和角色,所有业务数据走文件系统。


qingcheng.db — 数据库

位置:data/qingcheng.db(SQLite)

users 表 — 用户账号

python
class UserModel(Base):
    __tablename__ = "users"
    id            = Column(Integer, primary_key=True)   # 用户 ID
    username      = Column(String, unique=True)         # 登录名(全局唯一)
    password      = Column(String)                      # 密码(明文)
    role          = Column(String)                      # "admin" / "teacher" / "student"
    avatar_path   = Column(String)                      # 头像路径
    qc_descriptors = Column(Text)                       # JSON 字符串:自定义描述符
字段示例说明
id1自增主键,对应文件目录名users/teachers/1/
usernameArin登录账号,全局唯一
password111111明文存储,当前设计
roleteacher角色权限
avatar_pathavatar_01.jpg头像文件名
qc_descriptors{}{"teacher":"Arin"}JSON 字符串,info.json 中的 descriptors 来源

trash 表 — 回收站

python
class TrashModel(Base):
    __tablename__ = "trash"
    id             = Column(Integer, primary_key=True)
    username       = Column(String)          # 被删用户名
    role           = Column(String)          # 被删用户角色
    original_path  = Column(String)          # 原文件路径
    trash_folder   = Column(String)          # 回收站文件夹名
    db_snapshot    = Column(Text)            # JSON:用户数据快照(用于恢复)
    trashed_at     = Column(String)          # 删除时间

数据库 vs 文件系统

维度users 表(qingcheng.db)文件系统(data/)
用户标识id = 5students/5/ ← 目录名 = id
登录凭证usernameinfo.json 里的 username 是冗余
密码password不存文件系统
角色roleinfo.json 的 role(冗余存储)
描述符qc_descriptorsinfo.json 的 descriptors(冗余存储)
业务数据classes.json, sessions/, notes/, messages/

关键理解: DB 和文件系统通过 id(目录名)关联。DB 管登录校验,文件系统管业务数据。两者是引用关系,不是同步关系。

关键理解: DB 和文件系统通过 id(目录名)关联。DB 管登录校验,文件系统管业务数据。两者是引用关系,不是同步关系。


管理员 (admin)

admins/{id}/
└── info.json

管理员只有基本账号信息,没有班级、课堂记录等业务数据。

数据库中: users 表,role = "admin"

相关 API:

  • POST /api/register — 注册教师账号(仅 admin 可操作)
  • DELETE /api/users/{username} — 删除用户(移至回收站)
  • GET /api/trash — 查看回收站
  • POST /api/trash/restore/{id} — 恢复被删用户
  • DELETE /api/trash — 清空回收站

教师 (teacher)

teachers/{id}/
├── info.json              ← 基本信息
├── classes.json           ← 班级花名册(核心)
└── sessions/              ← 课堂记录
    ├── 初级班/
    │   ├── 2026-07.json
    │   └── 2026-06.json
    └── 高级班/
        └── 2026-07.json

info.json

json
{
  "username": "Arin",
  "role": "teacher",
  "avatar_path": "avatar_01.jpg",
  "descriptors": {},
  "subfolders": []
}
  • descriptors — 自定义描述符(JSON 对象),存储在 DB qc_descriptors 字段
  • subfolders — 教师为 [](学生有 ["sb3_projects", "notes"]

classes.json — 班级花名册(核心)

json
[
  {
    "name": "初级班",
    "schedule": ["2026-07-13"],
    "students": [
      { "user_id": 5, "name": "张三" },
      { "user_id": 8, "name": "李四" }
    ]
  },
  {
    "name": "高级班",
    "schedule": ["2026-07-14"],
    "students": [
      { "user_id": 12, "name": "王五" }
    ]
  }
]
字段类型说明
namestring班级名称,同教师下唯一
schedulestring[]上课日期列表,UI 未使用此字段
studentsobject[]班级学生列表
students[].user_idint对应 DBusers 表的 id
students[].namestring学生显示名

关键设计:

  • classes.json师生归属关系的唯一凭证
  • 删除学生 = 从 students 移除该 user_id(历史记录不受影响)
  • 同一学生可在不同老师的 classes.json 中出现(跨老师转班)
  • 同名不同人需手动加后缀(如张三(小明)、张三(小红))

API:

方法路由作用
GET/api/classes获取当前教师的所有班级
POST/api/classes创建班级
PUT/api/classes/{name}修改班级名称(同步重命名 sessions 目录)
DELETE/api/classes/{name}删除班级(移到回收站,sessions 历史保留)
POST/api/classes/{name}/students添加学生
DELETE/api/classes/{name}/students/{id}从班级移除学生

sessions — 课堂记录

sessions/{班级}/{YYYY-MM}.json
json
[
  {
    "date": "2026-07-13",
    "students": [
      { "name": "张三", "remark": "", "status": "present",
        "content": "循环结构掌握良好", "next_plan": "函数入门" },
      { "name": "试听生", "remark": "临时",
        "status": "present", "content": "正常推进", "next_plan": "正常推进" }
    ]
  }
]
字段类型说明
datestring上课日期YYYY-MM-DD,同班级下唯一,覆盖保存
students[].namestring学生姓名
students[].remarkstring"临时" = 临时学生(不在 classes.json 中)
students[].statusstring"present" 出勤 / "absent" 缺勤
students[].contentstring本节课内容
students[].next_planstring下节课计划

关键设计:

  • 删除班级不删除 sessions 目录,历史记录永远保留
  • 班级改名时 sessions 目录同步重命名
  • 临时学生只存在此处,不影响 classes.json

API:

方法路由作用
GET/api/sessions/{class}获取某班级某月的课堂记录
POST/api/sessions/{class}保存某日期的课堂记录(覆盖)
DELETE/api/sessions/{class}/{date}删除某日期的课堂记录
GET/api/sessions/student/{name}全局扫描所有教师 sessions,查学生全部历史

学生 (student)

students/{id}/
├── info.json              ← 基本信息
├── sb3_projects/          ← Scratch 作品
├── notes/                 ← 笔记本 + .md 笔记
│   ├── Scratch/
│   │   ├── 入门.md
│   │   └── 循环结构.md
│   └── Python/
│       └── 变量.md
└── messages/              ← 站内信
    └── 收件箱/
        └── 20260713143052_Arin.json

info.json

json
{
  "username": "张三",
  "role": "student",
  "avatar_path": "avatar_37.jpg",
  "descriptors": { "teacher": "Arin" },
  "subfolders": ["sb3_projects", "notes"]
}
  • descriptors.teacher — 创建该学生的教师(仅标签,不参与权限校验)

站内信

json
{
  "id": "20260713143052_Arin",
  "sender": "Arin",
  "subject": "课堂笔记",
  "body": "支持 **Markdown** 语法",
  "created_at": "2026-07-13 14:30:52",
  "is_read": false
}

学生与教师的关联逻辑

关联通过 classes.jsonuser_id 引用实现,不是 DB 外键

DB: users 表                     → 只管登录、角色权限
FS: classes.json(教师的文件)    → 管师生归属
        ↓ 通过 user_id 引用 DB
        ↓ 通过全局扫描 sessions 找学生历史

跨教师查询: /api/sessions/student/{name} 扫描所有教师的 sessions/ 目录,所以学生的课堂记录不受换班、换老师影响。

文件关系:

teacher → classes.json(花名册)
    ├── 班级 A → students: [张三, 李四]
    │                └── sessions/A/ → 张三的记录
    └── 班级 B → students: [王五, 张三]
                     └── sessions/B/ → 张三的记录
                           ↓ 全局扫描
                          张三的全部历史