架构总览
OpSkool 是 一个底座 + 一组可拼装的能力积木。代码用 pnpm workspace + turbo 组成单仓库,分四层,层与层之间的依赖方向不是口头约定——是机器强制的。
四层结构
章节:四层结构apps/ 可执行进程 web/ Next.js App Router(含 modules-registry.ts 装配点) jobs/ 定时任务 worker(含自己的 modules-registry.ts)modules/ 底座与主数据积木(不得 import domains/*) identity/ 登录、会话、成员、org、scope_units(校区的权威名源) permissions/ 角色权限、withOrg 事务 + RLS 上下文 platform/ 日志、时钟 now()、错误映射等基础设施 entitlements/ 模块开通判定:来源仲裁 / registry / 额度 / 用量 / 过期 catalog/ 花名册积木:老师·学生·教室·学科 / 班级 / Excel 导入 dashboard/ 看板零件(scheduling 的一部分,不单列积木) sources/ 数据源 CRUD(本地面;外部接入的实现不在社区版)domains/ 能力积木(有独立业务闭环) scheduling/ 排课:冲突、物化、学期节奏、打印导出 collection/ 收集:任务、受众、提交、催办 attendance/ 点名与课消:课时台账、点名扣课消、经营报表四区块、家长单 grades/ 成绩与曲线:考试、成绩录入、成绩序列、成绩单packages/ 纯包(零应用层依赖) shared-schema/ zod 契约、错误码、ModuleManifest conflict-engine/ 纯算法冲突引擎 ui/ 共享控件库(自持皮肤)db/ drizzle schema + 迁移 + seedDockerfile web 与 jobs 共用的多阶段镜像deploy/ compose(demo / 生产 / 镜像·演示站 override)/ Caddyfilescripts/ 边界检查等本地工装逐层的职责:
| 层 | 里面放什么 | 判别标准 |
|---|---|---|
apps/ | 可执行进程:web 与 jobs | 有 main、能被 docker run 起来的东西 |
modules/ | 底座与主数据 | 别的东西要用它,但它自己不构成一条业务闭环 |
domains/ | 能力积木 | 有独立业务闭环,能被整块开通或关闭 |
packages/ | 纯包 | 不认识任何应用层概念,可以原样搬到别的项目 |
五条依赖铁律
章节:五条依赖铁律由 .dependency-cruiser.cjs 定义,在 make check 里执行,违反即红:
no-modules-import-domains——modules/* 不得 import domains/*。底座与主数据不能反过来依赖能力积木,否则「关掉排课」就会把花名册也拖下水,积木的可拆装性当场消失。
no-pure-packages-import-app-layers——packages/* 不得 import apps / modules / domains。纯包一旦认识了应用层概念就不再纯,conflict-engine 里就会长出「机构」「校区」这类只有本产品才有的词,再也没法单独测试和替换。
no-identity-import-business——modules/identity 不得 import 任何业务概念。它和 db/schema/identity.ts 合起来是一套永不认识业务的通用身份 + RBAC 核心:它知道「用户」「角色」「范围单元」,但不知道「班级」「课次」。这条守住了,身份核心才能在产品形态变化时原样活下来。
no-closed-package-imports——apps/、modules/、domains/、packages/、db/ 都不得静态 import 闭源积木的包名。闭源积木只能由各 apps/*/modules-registry.ts 在运行时按
OPSKOOL_CLOSED_MODULES(见环境变量参考)动态加载——这条守住了「开源核心的代码里找不到任何一行写死的闭源包引用」,即使本机装了闭源包也一样。
no-raw-db-client-outside-permissions——除 modules/permissions 外不得直接拿裸数据库客户端。所有业务查询必须走 withOrg 事务入口,事务内 SET LOCAL app.org_id 打开 RLS 上下文。绕过它 = 绕过租户隔离,见多租户与行级安全。
为什么是机械强制而不是约定
章节:为什么是机械强制而不是约定约定会腐化。赶工期的那天、新人入职的那周、「就这一次」的那个 PR,边界就破了,而破掉的边界不会自己报警——它只会在半年后表现为「这块拆不下来了」。
make check 里的 depcruise 让违反变成一次构建失败。规则不会累,不会赶工期,不会通融。
技术栈
章节:技术栈TypeScript 全栈 · Next.js App Router · PostgreSQL(行级安全多租户)· pnpm workspace + turbo 单仓库 · Docker Compose 部署。
社区版不随附测试用例,pnpm run check(lint + 类型检查 + 依赖方向检查)与 pnpm build
是社区版可用的质量门。
本页是 CE README「仓库结构」一节的展开;两处不一致时,以 README 为准。
© 2026 OpSkool 办学帮 · 苏ICP备2025224982号-3