校园背景的项目系统,往往存在每年都要给学生更换年级的需求。此文章用于记录部分实现思路。
需求:
1.创建班级时仅选择学段与入学年份,不选择年级。
2.每年自动更新一次班级升年级。
3.升学至下一学段时,入学年份需要变更。
一、表设计与示例数据
1.study_section 学段表
字段名 | 字段类型 | 字段注释 |
---|---|---|
section_id | int | 学段id |
section_name | vchar | 学段名称 |
sort | int | 排序序号 |
gradeNum | int | 有效年级数 |
学段示例数据
学段id | 学段名称 | 排序序号 | 有效年级数 |
---|---|---|---|
1 | 小学 | 1 | 6 |
2 | 初中 | 2 | 3 |
3 | 高中 | 3 | 3 |
2.study_grade 年级表
字段名 | 字段类型 | 字段注释 |
---|---|---|
grade_id | int | 年级id |
section_id | int | 学段id |
grade_name | vchar | 年级名称 |
sort | int | 排序序号 |
年级示例数据
年级id | 学段id | 年级名称 | 排序序号 |
---|---|---|---|
1 | 1 | 一年级 | 1 |
2 | 1 | 二年级 | 2 |
3 | 1 | 三年级 | 3 |
4 | 1 | 四年级 | 4 |
5 | 1 | 五年级 | 5 |
6 | 1 | 六年级 | 5 |
7 | 1 | 未安排 | 6 |
8 | 1 | 已毕业 | -1 |
9 | 2 | 初一 | 1 |
10 | 2 | 初二 | 2 |
11 | 2 | 初三 | 3 |
12 | 2 | 未安排 | 4 |
13 | 2 | 已毕业 | -1 |
14 | 3 | 高一 | 1 |
15 | 3 | 高二 | 2 |
16 | 3 | 高三 | 3 |
17 | 3 | 未安排 | 4 |
18 | 3 | 已毕业 | -1 |
3.study_class 班级表
字段名 | 字段类型 | 字段注释 |
---|---|---|
class_id | int | 班级id |
enrollment_year | int | 入学年份 |
grade_id | int | 年级id |
class_num | int | 班号 |
class_name | vchar | 班级名称 |
班级示例数据 (当前是2022年10月)
班级id | 入学年份 | 年级id | 班号 | 班级名称 |
---|---|---|---|---|
1 | 2022 | 1 | 1 | 2022级小学1班(一年级) |
2 | 2019 | 3 | 1 | 2019级小学1班(三年级) |
3 | 2020 | 2 | 2 | 2020级初中2班 (初三) |
4 | 2021 | 2 | 4 | 2021级初中4班(初二) |
5 | 2022 | 3 | 5 | 2022级高中5班 (高一) |
二、逻辑代码
1.创建班级
不可存在同入学年份、同学段、同班号的班级
public void addClass(StudyClassDTO classDTO) {
// 1.先根据入学年份、学段获取年级
Long gradeId = getGradeIdBySectionAndYear(classDTO.getSectionId(), classDTO.getEnrollmentYear());
// 2.判断入学年份、年级、班号是否存在重复数据
QueryWrapper<StudyClass> queryCla = new QueryWrapper<>();
queryCla.lambda().eq(StudyClass::getEnrollmentYear, classDTO.getEnrollmentYear());
queryCla.lambda().eq(StudyClass::getClassNum, classDTO.getCalssNum());
queryCla.lambda().eq(StudyClass::getGradeId, gradeId);
List<StudyClass> classList = studyClassMapper.selectList(queryCla);
if (CollectionUtils.isNotEmpty()) {
throw new ServiceException("已存在同年份、同学段、同班号班级!");
}
// 3.插入数据
StudyClass addClass = new StudyClass();
BeanUtils.copy(classDTO, param);
param.setGradeId(gradeId);
studyClassMapper.insert(addClass);
}
/**
* 根据传入的年份、学段获取年级
* 默认按传入年份的九月开始算有效学期
*/
public Long getGradeIdBySectionAndYear(Long sectionId, Integer enrollmentYear) {
// 1.查询入学年份与当前年份的间隔年数
// 获取当前年份、月份,不可传入今年之后的年份
int nowYear = Calendar.getInstance().get(Calendar.YEAR);
int yearNum = nowYear - enrollmentYear.intValue(); // 同年则为0
// 判断入学年份是否正确,根据学段查询有效的年级范围
QueryWrapper<StudySection> querySec = new QueryWrapper<>();
querySec.lambda().eq(StudySection::getSectionId, sectionId);
StudySection studeySection = studySectionMapper.selectOne(querySec);
Integer gradeNum = studeySection.getGradeNum();
if(yearNum < 0 || yearNum > gradeNum-1){
throw new ServiceException("入学年份不正确!");
}
// 根据学段查询年级id数组
querySec.lambda().orderByAsc(StudyGrade::getSort);
List<StudyGrade> gradeList = studyGradeMapper.selectList(querySec)
if (CollectionUtils.isEmpty(gradeList)) {
throw new ServiceException("该学段下暂无年级信息!");
}
if(yearNum >= gradeList.size()-1){
throw new ServiceException("该学生已经毕业!");
}
return gradeList.get(yearNum);
}
2.自动升年级定时器
思路:计算出班级当前年级的下一级,并批量替换。毕业年级排序为-1
(1) 待升级班级对象
// 待升级班级对象
public class UpClassInfo {
private Long classId; //班级id
private Long gradeId; //年级id
private Integer gradeNumber; //班级当前年级排序 -1代表已毕业
private Long sectionId; // 学段id
private Integer gradeNum; // 学段的有效年级数
}
(2)定时任务代码
public void upClassLevel() {
List<StudyClass> updateClassList = new ArrayList<>();
// 1. 查询当前有效的班级列表(含当前年级排序、当前年级学段有效年数)
List<UpClassInfo> upClassList = {查询sql...};
if (CollectionUtils.isNotEmpty(upClassList)) {
// 2.查询各学段年级的学段、年级排序以及年级id对应Map
Map<String, Long> sectionGradeMap = new HashMap<>();
// 偷懒复用一下对象
List<UpClassInfo> sectionGradeList = {查询sql...};
if (CollectionUtils.isNotEmpty(sectionGradeList)) {
sectionGradeMap = sectionGradeList.stream().collect(Collectors.toMap(sg -> sg.getSectionId() + "_" + sg.getGradeNumber, UpClassInfo::getGradeId, (sg1, sg2) -> sg1));
}
for (UpClassInfo upClass : upClassList) {
Integer nextGradeNumber = upClass.getGradeNumber();
if (upClass.getGradeNumber() + 1 > upClass.getGradeNum) {
// 如果班级当前的年级排序已超过学段年级数范围,则判断为毕业(排序为-1)
nextGradeNumber = -1;
} else {
// 否则正常取下一级年级
nextGradeNumber = nextGradeNumber + 1;
}
StudyClass updateClass = new StudyClass();
updateClass.setClassId(upClass.getClassId());
updateClass.setGradeId(sectionGradeMap.get(upClass.getSectionId() + "_" + -1));
// 加入待更新班级列表
updateClassList.add(updateClass);
// 记录班级年级变更日志
...
}
if (CollectionUtils.isNotEmpty(upateClassList)) {
// 批量更新
studyClass.updateBatch(upateClassList);
}
}
}
三、问题拓展
如果是含多个学段的学校应该如何处理?
基础教育学校包含:小学、初中、高中三个学段。部分学校含有多个学段。
查询学校关联的学段范围,如果有小学、初中、高中任意相邻学段(怎么样算相邻学段?),则不处理毕业?而是继续升学至下一学段的年级,并修改入学年份。
相邻学段:学段的排序相邻