882 lines
28 KiB
Markdown
882 lines
28 KiB
Markdown
# 教培系统架构设计与开发
|
||
|
||
本项目旨在开发一个基于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 做架构优化
|
||
> - 添加业务模块:课程体系设计,
|
||
> -
|
||
> - 生成前端
|
||
> - 完善脚手架....
|
||
|