From 697a809a77b289e7fdf480b61d106e3a2ba714fa Mon Sep 17 00:00:00 2001 From: Orangeeee Date: Wed, 23 Sep 2026 20:34:34 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=BB=E6=96=87=E4=BB=B6README.md=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +- RAEDME.md | 881 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 883 insertions(+), 2 deletions(-) create mode 100644 RAEDME.md diff --git a/.gitignore b/.gitignore index 18a6412..0f4e871 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,8 @@ __pycache__/ tests/ .git/ .github/ -*.md -!README.md +#*.md +#!README.md .env .env.* Dockerfile diff --git a/RAEDME.md b/RAEDME.md new file mode 100644 index 0000000..674a4df --- /dev/null +++ b/RAEDME.md @@ -0,0 +1,881 @@ +# 教培系统架构设计与开发 + +本项目旨在开发一个基于FastAPI的学生管理系统,提供**学生基本信息管理、考核成绩管理和就业管理**三大核心功能模块。 + +系统将采用RESTful API设计,支持前后端分离架构。 + +## 痛点分析 - 沃林专项 + +- 部门系统与产品 <- 没有拳头产品 - 给外面演示的系统,产品 + - 内部OA系统都没有接AI,用的是飞书大厂的平台 + - 学员就业信息等还是纸质存储,没有信息化 + - 拳头产品,**自身没有服务外部公司的团队与技术能力** - 目前**只局限培训** +- 教学课程开发 <- 课程体系太单一 + - 只有入门-项目,不够灵活,满足专业3-5年的诉求 + - 入门课程没有采用最新工具,**框架现代化API,很容易出去被pass - 外包等** + - 只有30-60人班级,没有**小班制进阶,专家培训** +- 客户群体拓展 <- 更多是跨行想入门的小白, +- 风险把控 <- 后付款的包就业模式,资金链如何把控? + +## 需求分期 + +团队对需求管理按照模块化解耦拆分开发,按照三个阶段开发 + +1. 基础需求:按照开发任务,完成5 + 1 统计模块的开发 + +2. 特色需求: + + - 数据源配置 + + - 鉴权管理 + + - 添加部门管理,顾问管理模块 + + - 添加用户认证模块 + + - 开发前端应用 + + - 开发更多的分析查询接口 + + - 增加AI智能体和知识库开发 + +3. 脚手架集成 + + + + + +### 第一阶段 - 基础需求 + +按照开发任务,完成5 + 1 统计模块的开发 + +- 学生 +- 班级 +- 老师 +- 成绩 +- 就业 +- 统计分析 + + + + + +### 第二阶段 - 特色需求 + +- 数据源配置 + +- 鉴权管理 + +- 添加部门管理,顾问管理模块 + +- 添加用户认证模块 + +- 开发前端应用 + +- 开发更多的分析查询接口 + +- 增加AI智能体和知识库开发 + + + + + + + + + +### 第三阶段 - 脚手架集成 + +核心组件 + +- 服务状态监控 - utc时区[还有哪些可替代?] + - db ,中间件等组件是否活跃 + - 自身服务是否活跃 +- 认证模块: oauth2_scheme + - +- + + + +## 业务拆分 + +> 业务范围划分 +> +> - 实体 +> - 学生基本信息管理 +> - 考核成绩管理 +> - 就业管理 +> - 班级 +> - 老师 +> - 统计分析 + + + + + + + +## 数据架构设计 + + + +### 数据关系 + +- 学生:班级 = N: 1, 班级:老师 = 1 : 1 / [N:1], 学生: 成绩 = 1:N , 学生:就业 = 1:1, +- 统计分析: + + + +### 表结构设计 + +```sql +-- create database student_management_system; +-- use student_management_system; +create database sms; +use sms; + +-- students +create table if not exists students( + id int auto_increment primary key, + num varchar(30) not null comment '学生编号', + name varchar(30) not null default '', + age int not null default 18, + sex tinyint not null default 0 comment '0未知 1男 2女', + + home_place varchar(50) null comment '家乡', + college varchar(50) null comment '毕业院校', + specialty varchar(50) null comment '专业', + enrollment_time datetime null comment '入学时间', + graduate_time datetime null comment '毕业时间', + education varchar(50) null comment '学历', + + -- 外键字段 + class_id int not null default 0, + + advisor_id int null comment '顾问id', + -- advisor_num int null comment '顾问编号', -- join teacher + + -- 通用字段 + create_time datetime not null default current_timestamp, + update_time datetime not null default current_timestamp on update current_timestamp, + is_deleted tinyint not null default 0, + delete_time datetime null comment '删除时间,记录软删除时间' +); + +-- teachers +create table if not exists teachers( + id int auto_increment primary key, + num varchar(30) not null comment '老师编号', + name varchar(30) not null, + age int not null, + sex tinyint not null default 0 comment '0未知 1男 2女', + + home_place varchar(50) null comment '家乡', + college varchar(50) null comment '毕业院校', + specialty varchar(50) null comment '专业', + enrollment_time datetime null comment '入学时间', + graduate_time datetime null comment '毕业时间', + education varchar(50) null comment '学历', + + -- 额外信息:工作经验/教学评级 ,教学方向:应用,项目,算法; + work_experience text null comment '工作经验', + coach_area varchar(50) null comment '教学方向:应用,项目,算法', + + -- 通用字段 + create_time datetime not null default current_timestamp, + update_time datetime not null default current_timestamp on update current_timestamp, + is_deleted tinyint not null default 0, + delete_time datetime null comment '删除时间,记录软删除时间' +); + + +-- classes +create table if not exists classes( + id int auto_increment primary key, + num varchar(30) not null comment '班级编号', + name varchar(30) not null comment '班级名称', + class_start_time datetime null comment '开课时间', + + head_teacher_id int null comment '班主任', + -- head_teacher_num int null comment '班主任编号', + + coach_teacher_id int null comment '授课老师', + -- coach_teacher_num int null comment '授课老师编号', + + tutor_teacher_id int null comment '助教老师', + -- tutor_teacher_num int null comment '助教老师编号', + + -- 通用字段 + create_time datetime not null default current_timestamp, + update_time datetime not null default current_timestamp on update current_timestamp, + is_deleted tinyint not null default 0, + delete_time datetime null comment '删除时间,记录软删除时间' +); + + +-- scores +create table if not exists scores( + id int auto_increment primary key, + sid int not null, + -- snum varchar(30) not null, + + num varchar(30) not null comment '考核序次', + score decimal(10,2) not null, + + -- 通用字段 + create_time datetime not null default current_timestamp, + update_time datetime not null default current_timestamp on update current_timestamp, + is_deleted tinyint not null default 0, + delete_time datetime null comment '删除时间,记录软删除时间' +); + + +-- employment +create table if not exists employment( + id int auto_increment primary key, + sid int not null, + -- snum varchar(30) not null, + -- sname varchar(30) not null, + + class_id int not null, + -- class_num varchar(30) not null comment '班级编号', + + employment_open_time datetime null comment '就业开放时间', + offer_recived_time datetime null comment 'offer下发时间', + employment_company varchar(50) null comment '就业公司', + employment_salary decimal(20,5) null comment '就业薪资', + + -- 通用字段 + create_time datetime not null default current_timestamp, + update_time datetime not null default current_timestamp on update current_timestamp, + is_deleted tinyint not null default 0, + delete_time datetime null comment '删除时间,记录软删除时间' +); +``` + + + +### 造数据压测 + +#### qa + +- cout / max 对聚合的支持数据库实现区别? +- + + + +#### 小范围数据测试 + +```sql +-- use student_management_system; +use sms; + +-- ==================== 清空旧数据(按依赖倒序) ==================== +set foreign_key_checks = 0; +truncate table employment; +truncate table scores; +truncate table students; +truncate table classes; +truncate table teachers; +set foreign_key_checks = 1; + +-- ==================== teachers ==================== +insert into teachers +(num, name, age, sex, home_place, college, specialty, + enrollment_time, graduate_time, education, work_experience, coach_area) +values +('T001', '张伟', 35, 1, '北京', '清华大学', '计算机科学与技术', + '2008-09-01', '2012-06-30', '本科', '10年Java教学经验', '应用'), +('T002', '李娜', 32, 2, '上海', '复旦大学', '软件工程', + '2010-09-01', '2014-06-30', '硕士', '8年前端教学经验', '项目'), +('T003', '王强', 40, 1, '广州', '中山大学', '人工智能', + '2004-09-01', '2008-06-30', '博士', '12年算法教学经验', '算法'), +('T004', '赵敏', 28, 2, '深圳', '浙江大学', '数据科学', + '2014-09-01', '2018-06-30', '硕士', '5年Python教学经验', '应用'), +('T005', '陈刚', 38, 1, '成都', '电子科技大学', '通信工程', + '2006-09-01', '2010-06-30', '本科', '11年嵌入式教学经验', '项目'); + +-- ==================== classes ==================== +insert into classes +(num,name, class_start_time, head_teacher_id, coach_teacher_id, tutor_teacher_id) +values +('C2024001','C2024001', '2024-03-01 09:00:00', 1, 2, 4), +('C2024002','C2024001', '2024-04-01 09:00:00', 2, 3, 5), +('C2024003','C2024003', '2024-05-01 09:00:00', 3, 1, 4); + +-- ==================== students ==================== +insert into students +(num, name, age, sex, home_place, college, specialty, + enrollment_time, graduate_time, education, class_id, advisor_id) +values +('S2024001', '刘洋', 22, 1, '北京', '北京大学', '计算机科学与技术', + '2020-09-01', '2024-06-30', '本科', 1, 1), +('S2024002', '孙丽', 23, 2, '上海', '上海交通大学', '软件工程', + '2019-09-01', '2023-06-30', '本科', 1, 1), +('S2024003', '周杰', 24, 1, '广州', '华南理工大学', '人工智能', + '2018-09-01', '2022-06-30', '硕士', 1, 2), +('S2024004', '吴敏', 21, 2, '深圳', '深圳大学', '数据科学', + '2021-09-01', '2025-06-30', '本科', 2, 3), +('S2024005', '郑浩', 25, 1, '成都', '四川大学', '通信工程', + '2017-09-01', '2021-06-30', '本科', 2, 4), +('S2024006', '冯雪', 22, 2, '杭州', '浙江大学', '计算机科学与技术', + '2020-09-01', '2024-06-30', '本科', 2, 5), +('S2024007', '黄磊', 23, 1, '南京', '南京大学', '软件工程', + '2019-09-01', '2023-06-30', '本科', 3, 1), +('S2024008', '徐婷', 24, 2, '武汉', '武汉大学', '人工智能', + '2018-09-01', '2022-06-30', '硕士', 3, 2), +('S2024009', '马超', 21, 1, '西安', '西安交通大学', '数据科学', + '2021-09-01', '2025-06-30', '本科', 3, 3), +('S2024010', '朱琳', 22, 2, '天津', '南开大学', '通信工程', + '2020-09-01', '2024-06-30', '本科', 3, 4); + +-- ==================== scores ==================== +insert into scores (sid, num, score) values +-- 刘洋 +(1, '第一次考核', 85.50), (1, '第二次考核', 90.00), (1, '第三次考核', 88.00), +-- 孙丽 +(2, '第一次考核', 92.00), (2, '第二次考核', 87.50), (2, '第三次考核', 91.00), +-- 周杰 +(3, '第一次考核', 78.00), (3, '第二次考核', 82.50), (3, '第三次考核', 80.00), +-- 吴敏 +(4, '第一次考核', 95.00), (4, '第二次考核', 93.50), (4, '第三次考核', 96.00), +-- 郑浩 +(5, '第一次考核', 70.00), (5, '第二次考核', 75.50), (5, '第三次考核', 72.00), +-- 冯雪 +(6, '第一次考核', 88.00), (6, '第二次考核', 90.50), (6, '第三次考核', 89.00), +-- 黄磊 +(7, '第一次考核', 82.00), (7, '第二次考核', 85.00), (7, '第三次考核', 83.50), +-- 徐婷 +(8, '第一次考核', 91.00), (8, '第二次考核', 94.00), (8, '第三次考核', 92.50), +-- 马超 +(9, '第一次考核', 76.00), (9, '第二次考核', 79.50), (9, '第三次考核', 77.00), +-- 朱琳 +(10, '第一次考核', 89.00), (10, '第二次考核', 86.50), (10, '第三次考核', 90.00); + +-- 提供不及格数据 +insert into students +(num, name, age, sex, home_place, college, specialty, + enrollment_time, graduate_time, education, class_id, advisor_id) +values +('S2026001', '牧遥', 22, 1, '北京', '北京大学', '计算机科学与技术', + '2020-09-01', '2024-06-30', '本科', 1, 1); +insert into scores (sid, num, score) values +-- 牧遥 +(11, '第一次考核', 56.50), (11, '第二次考核', 30.00), (11, '第三次考核', 88.00); + + +-- ==================== employment ==================== +insert into employment +(sid, class_id, employment_open_time, offer_recived_time, + employment_company, employment_salary) +values +(1, 1, '2024-06-01 00:00:00', '2024-07-15 10:00:00', '字节跳动', 25000.00000), +(2, 1, '2024-06-01 00:00:00', '2024-07-20 14:30:00', '腾讯', 28000.00000), +(3, 1, '2024-06-01 00:00:00', '2024-08-01 09:00:00', '阿里巴巴', 30000.00000), +(4, 2, '2024-07-01 00:00:00', '2024-08-10 11:00:00', '美团', 24000.00000), +(5, 2, '2024-07-01 00:00:00', null, null, null), +(6, 2, '2024-07-01 00:00:00', '2024-08-15 16:00:00', '京东', 26000.00000), +(7, 3, '2024-08-01 00:00:00', '2024-09-05 10:30:00', '百度', 27000.00000), +(8, 3, '2024-08-01 00:00:00', '2024-09-10 15:00:00', '华为', 32000.00000), +(9, 3, '2024-08-01 00:00:00', null, null, null), +(10, 3, '2024-08-01 00:00:00', '2024-09-20 09:30:00', '小米', 23000.00000); +``` + + + + + +##### 统计大盘 - 示例sql + +```sql +-- 基本信息统计 +select * from students where age > 30; + +select class_id, sex, count(*) from students group by class_id,sex; + + +-- 成绩统计 +select s.num,s.name, sc.score from students s left join scores sc on s.id = sc.sid where sc.score > 80; + +-- 两次不及格 -> CTE 更好点: <- todo | 代码实现角度:? +select s.name 学生名称,c.name 班级名称 ,tmp.score 学生成绩 +from + students as s, classes as c, (select sid,score from scores where score < 60 group by sid, score) as tmp , +(select sid from (select sid,score from scores where score < 60 group by sid, score) as tmp +group by sid having count(*) >= 2 +) as sid_table + +where s.id = sid_table.sid + and s.class_id = c.id + and s.id = tmp.sid; + +-- 或者:全量join,然后2次group by + +-- +select s.class_id, sc.num, avg(sc.score) +from students s left join scores sc +on s.id = sc.sid +group by sc.num, s.class_id +order by avg(sc.score) desc; + + +-- 就业统计 + +select s.name, c.name, + e.employment_open_time, e.offer_recived_time, + e.employment_company,e.employment_salary +from students s + left join classes c on s.class_id = c.id + left join employment e on s.id = e.sid +order by e.employment_salary desc +limit 5; + + +select s.name, c.name, + e.employment_open_time, e.offer_recived_time, + e.employment_company,e.employment_salary, + (e.offer_recived_time - e.employment_open_time) as duration +from students s + left join classes c on s.class_id = c.id + left join employment e on s.id = e.sid; + + +select s.class_id, avg(e.offer_recived_time -e.employment_open_time) +from students s + left join classes c on s.class_id = c.id + left join employment e on s.id = e.sid +where + e.employment_open_time is not null +group by s.class_id; +``` + + + + + +```md +employment_open_time datetime null comment '就业开放时间', + offer_recived_time datetime null comment 'offer下发时间', + employment_company varchar(50) null comment '就业公司', + employment_salary decimal(20,5) null comment '就业薪资', + +``` + + + + + +#### 学生表 - 数据量瓶颈是多少? - 千万?亿级 + + + + + + + + + +#### 分库分表 - 考虑历史数据与最新数据 + + + + + + + +### 表结构优化 + + + +#### 通用字段方案 + +##### 框架层 - ORM 基类 + +```python +from sqlalchemy import Column, Integer, String, DATETIME, create_engine, Boolean +from datetime import datetime + +# 定义基类模板,解决通用字段 +class Template(): + id = Column( Integer # 声明字段的数据类型 + , primary_key=True # 声明是主键 + , autoincrement=True # 声明是自增主键 + , comment='订单编号,自增主键' # 注释 + ) + create_datetime = Column(DATETIME + , default=datetime.now # 默认值是当前时间 + ) + update_datetime = Column(DATETIME + , default=datetime.now # 首次创建的时间跟更新时间一致 + , onupdate=datetime.now + ) + is_deleted = Column(Boolean, default=False ) + delete_datetime = Column(DATETIME, nullable=True) + +``` + + + +##### triger deletetime + +```sql +delimiter $$ +create trigger trg_students_update_time +before update on students +for each row +begin + -- 只在业务字段变化时更新 update_time,纯软删除操作不更新 + if not (old.is_deleted <=> new.is_deleted) + and old.sname <=> new.sname + and old.age <=> new.age then + set new.update_time = old.update_time; + else + set new.update_time = current_timestamp; + end if; +end$$ +delimiter ; +``` + + + + + +#### 索引与约束 + +##### 索引同步 + +| 表 | 索引名 | 类型 | 字段 | 对应查询场景 | +| -------------- | ----------------- | ---- | ------------------------ | -------------------------------- | +| **students** | PRIMARY | 主键 | `id` | 按主键查 | +| | uk_snum | 唯一 | `snum` | 按学号精确查 | +| | idx_class_id | 普通 | `class_id` | 按班级查学生列表 | +| | idx_advisor_id | 普通 | `advisor_id` | 按顾问查学生 | +| | idx_class_deleted | 联合 | `(class_id, is_deleted)` | 查某班未删除学生 | +| **teachers** | PRIMARY | 主键 | `id` | 按主键查 | +| | uk_tnum | 唯一 | `tnum` | 按老师编号精确查 | +| **classes** | PRIMARY | 主键 | `id` | 按主键查 | +| | uk_class_num | 唯一 | `class_num` | 按班级编号精确查 | +| | idx_head_teacher | 普通 | `head_teacher_id` | 按班主任查班级 | +| | idx_coach_teacher | 普通 | `coach_teacher_id` | 按授课老师查班级 | +| | idx_tutor_teacher | 普通 | `tutor_teacher_id` | 按助教查班级 | +| **scores** | PRIMARY | 主键 | `id` | 按主键查 | +| | uk_sid_score | 唯一 | `(sid, score_num)` | 防止同一学生同一考核序次重复录入 | +| | idx_snum | 普通 | `snum` | 按学号查成绩 | +| | idx_sid | 普通 | `sid` | 按学生 ID 查成绩 | +| **employment** | PRIMARY | 主键 | `id` | 按主键查 | +| | uk_sid | 唯一 | `sid` | 一个学生只能有一条就业记录 | +| | idx_class_id | 普通 | `class_id` | 按班级统计就业 | +| | idx_snum | 普通 | `snum` | 按学号查就业 | +| | idx_class_deleted | 联合 | `(class_id, is_deleted)` | 查某班未删除就业记录 | + +```sql +-- alter table students drop primary key -- auto_increment 列 必须是 key +-- alter table students drop index idx_email +-- ==================== students ==================== +alter table students + add unique key uk_snum (num), + add key idx_class_id (class_id), + add key idx_advisor_id (advisor_id), + add key idx_class_deleted (class_id, is_deleted); + +-- ==================== teachers ==================== +alter table teachers + add unique key uk_tnum (num); + +-- ==================== classes ==================== +alter table classes + add unique key uk_class_num (num), + add key idx_head_teacher (head_teacher_id), + add key idx_coach_teacher (coach_teacher_id), + add key idx_tutor_teacher (tutor_teacher_id); + +-- ==================== scores ==================== +alter table scores + add unique key uk_sid_score (sid, num), + -- add key idx_snum (snum), + add key idx_sid (sid); + +-- ==================== employment ==================== +alter table employment + add unique key uk_sid (sid), + add key idx_class_id (class_id), + -- add key idx_snum (snum), + add key idx_class_deleted (class_id, is_deleted); +``` + + + +##### 外键约束 + +| 表 | 字段 | 引用 | 含义 | +| -------------- | ------------------ | ------------- | ---------------- | +| **students** | `class_id` | `classes.id` | 学生所属班级 | +| **students** | `advisor_id` | `teachers.id` | 学生的顾问老师 | +| **classes** | `head_teacher_id` | `teachers.id` | 班主任 | +| **classes** | `coach_teacher_id` | `teachers.id` | 授课老师 | +| **classes** | `tutor_teacher_id` | `teachers.id` | 助教老师 | +| **scores** | `sid` | `students.id` | 成绩所属学生 | +| **employment** | `sid` | `students.id` | 就业记录所属学生 | +| **employment** | `class_id` | `classes.id` | 就业记录所属班级 | + +```sql +-- SET FOREIGN_KEY_CHECKS = 0; -- 临时关闭 +-- 批量导入 +-- SET FOREIGN_KEY_CHECKS = 1; + +-- ==================== students ==================== +alter table students + add constraint fk_students_class + foreign key (class_id) references classes(id) + on delete restrict on update cascade, + -- on delete set null on update cascade, + add constraint fk_students_advisor + foreign key (advisor_id) references teachers(id) + on delete set null on update cascade; + +-- ==================== classes ==================== +alter table classes + add constraint fk_classes_head_teacher + foreign key (head_teacher_id) references teachers(id) + on delete set null on update cascade, + add constraint fk_classes_coach_teacher + foreign key (coach_teacher_id) references teachers(id) + on delete set null on update cascade, + add constraint fk_classes_tutor_teacher + foreign key (tutor_teacher_id) references teachers(id) + on delete set null on update cascade; + +-- ==================== scores ==================== +alter table scores + add constraint fk_scores_student + foreign key (sid) references students(id) + on delete cascade on update cascade; + -- on delete set null on update cascade; + +-- ==================== employment ==================== +alter table employment + add constraint fk_employment_student + foreign key (sid) references students(id) + on delete cascade on update cascade, + -- on delete set null on update cascade, + add constraint fk_employment_class + foreign key (class_id) references classes(id) + on delete restrict on update cascade; + -- on delete set null on update cascade; +``` + + + + + +#### **字典表**: 解决字段规范问题 + +> **`education`、`college`、`specialty`** + +| 字段 | 所在表 | 问题 | 字典化收益 | +| ------------------------ | ------------------ | --------------------------- | ---------------------------------- | +| **`sex`** | students, teachers | 用 `int` 存,取值含义靠注释 | 统一“0未知/1男/2女” | +| **`education`** | students, teachers | 字符串自由填写 | 统一“本科/硕士/博士”等 | +| **`college`** | students, teachers | 字符串自由填写 | 统一院校名称 | +| **`specialty`** | students, teachers | 字符串自由填写 | 统一专业名称 | +| **`coach_area`** | teachers | `text` 存“应用/项目/算法” | 统一教学方向 | +| **`employment_company`** | employment | 字符串自由填写 | 可选,公司名变化快,不一定适合字典 | + +**判断标准**:取值**有限、稳定、需要跨表统一**的字段,适合字典化;取值**开放、频繁变化**的字段(如公司名、地址),不适合。 + + + +##### 通用字典表 + +```sql +create table if not exists dict_items ( + id int auto_increment primary key, + dict_type varchar(50) not null comment '字典类型:sex, education, college, specialty', + dict_code varchar(50) not null comment '字典编码,存到业务表的值', + dict_label varchar(100) not null comment '显示名称', + sort_order int not null default 0 comment '排序', + is_enabled tinyint not null default 1 comment '是否启用', + + create_time datetime not null default current_timestamp, + update_time datetime not null default current_timestamp on update current_timestamp, + is_deleted tinyint not null default 0, + delete_time datetime not null default null, + + unique key uk_type_code (dict_type, dict_code), + key idx_type (dict_type) +) engine=InnoDB default charset=utf8mb4; + +insert into dict_items (dict_type, dict_code, dict_label, sort_order) values +('sex', '0', '未知', 1), +('sex', '1', '男', 2), +('sex', '2', '女', 3), +('education', 'bachelor', '本科', 1), +('education', 'master', '硕士', 2), +('education', 'phd', '博士', 3), +('coach_area', 'app', '应用', 1), +('coach_area', 'project', '项目', 2), +('coach_area', 'algorithm', '算法', 3); +``` + + + + + +##### 每个维度独立建字典 - 适合高校+专业维度多 - 后期维护更专业! + +```sql +create table if not exists dict_colleges ( + id int auto_increment primary key, + college_code varchar(50) not null, + college_name varchar(100) not null, + is_enabled tinyint not null default 1, + unique key uk_code (college_code) +) engine=InnoDB default charset=utf8mb4; + +create table if not exists dict_specialties ( + id int auto_increment primary key, + specialty_code varchar(50) not null, + specialty_name varchar(100) not null, + college_id int not null comment '所属学院', + is_enabled tinyint not null default 1, + unique key uk_code (specialty_code), + key idx_college (college_id) +) engine=InnoDB default charset=utf8mb4; + +-- 全国高校,专业数据 +``` + + + + + +##### 业务表引用 + + + +###### dict_code - 采用 + +```sql +-- students.education 存 'bachelor',而不是 '本科' +select s.sname, d.dict_label as education_name +from students s +left join dict_items d + on d.dict_type = 'education' + and d.dict_code = s.education + and d.is_deleted = 0; +``` + + + +###### id - 简单,但是迁移 - 跨环境id自增导致不一致问题 + +```sql +select s.* , +from students s left join dict_items d +on s.college = d.id +``` + + + +###### 注意事项 + +> **软删除的字典项,业务表还在引用** <- 字典表更新 - 轻易不再动 +> +> - 如果某个字典项被软删除,但业务表里还有引用它的记录,查询时会 JOIN 不到,显示为空。 +> - **解决方案**:字典项不轻易删除,只设 `is_enabled = 0`(禁用),禁用后不允许新记录引用,但历史记录仍能 JOIN 到 +> +> 业务表引用字典表的 `dict_code`,**不建议加数据库外键** +> +> - 因为 `dict_code` 是字符串,且字典表可能被缓存到应用层,加外键会限制字典的灵活性。 +> +> - **用应用层校验**:插入业务记录前,先检查 `dict_code` 是否存在于字典表。 +> +> **字典数据量大了怎么办?** +> +> - 如果字典项很多(比如全国高校列表),`dict_items` 一张表会变得很大。 +> - **解决方案**:按 `dict_type` 分表,或者把大字典(如高校)单独建表。 + + + +| 方案 | 优点 | 缺点 | 适用场景 | +| ---------------- | -------------------- | -------------------- | ------------------ | +| **字典表** | 可动态维护、跨表统一 | 需 JOIN、需维护 | 取值有限且需要统一 | +| **枚举(ENUM)** | 数据库层面约束 | 改枚举要 DDL、不灵活 | 取值极少且永不变 | +| **直接存字符串** | 简单、无 JOIN | 容易不一致、难统计 | 取值开放、无需统一 | + + + + + +## 技术架构设计 + + + +### 构建环境适配 + +```bash +# uv 统一 +pip install uv +uv init +uv add fastapi[standard] pymsql sqlalchemy # 添加依赖 + +# 运行测试 +uv run uvicorn main:app --reload # uv run fastapi dev + +# 其他平台: sync 同步即可 +uv sync +``` + + + + + +### 基础架构适配 + +- 后端框架: + - web route / api docs / 数据校验 : FastAPI + - ORM 框架: pymysql, sqlarchemy + - 日志系统:loguru, + - +- 数据库:多数据源适配 + - dev : sqlite3 + - prod: MySQL, PG +- 前端框架: + - vue3 + - + + + + + + + +### 脚手架设计 - 单体架构 + + + + + + + +### 分布式架构 - 微服务 + +> 方向: +> +> - 把学生模块拆分出来 +> - + + + + + +## AI-agent接入 + +> 核心 +> +> - 让agent 做架构优化 +> - 添加业务模块:课程体系设计, +> - +> - 生成前端 +> - 完善脚手架.... +