@@ -47,6 +47,9 @@ yh:
|
|||||||
# 受信应用(服务端验签用):appKey -> appSecret
|
# 受信应用(服务端验签用):appKey -> appSecret
|
||||||
trusted-apps:
|
trusted-apps:
|
||||||
100617: 2a3b4c5d6e7f8091a2b3c4d5e6f70819
|
100617: 2a3b4c5d6e7f8091a2b3c4d5e6f70819
|
||||||
|
open:
|
||||||
|
# 班级管理(bschclass)服务基址:绑定班级页班级数据源(/oapi/sch/getSchClassList 等)
|
||||||
|
bschclass: http://base.yunhaixingtong.com/userclass
|
||||||
|
|
||||||
# 上传文件落容器卷,compose 里挂到宿主机 ./upload-data
|
# 上传文件落容器卷,compose 里挂到宿主机 ./upload-data
|
||||||
FILE_PATH: /data/upload
|
FILE_PATH: /data/upload
|
||||||
|
|||||||
+43
-24
@@ -172,31 +172,54 @@ ok(
|
|||||||
JSON.stringify(myTreeRows[0])
|
JSON.stringify(myTreeRows[0])
|
||||||
);
|
);
|
||||||
|
|
||||||
// 9.7 绑定班级:class 档案 → BindClass/UnbindClass/BatchUnbind → 年级 Tab 过滤
|
// 9.7 绑定班级:班级数据源=班级管理平台接口(bschclass,本应用不落库、无 Add/Delete)
|
||||||
const clsNameA = `冒烟高一班${stamp}`;
|
// → BindClass/UnbindClass/BatchUnbind → 年级 Tab 过滤
|
||||||
const clsNameB = `冒烟高二班${stamp}`;
|
// 前置:dev 身份 schId=3(云海中学)在平台须有高一/高二班级
|
||||||
const clsAddA = await api("POST", "/api/class/Add", { form: { className: clsNameA, grade: 1, studentCount: 45, headTeacher: "王老师" } });
|
const clsListAll = await api("GET", "/api/class/List");
|
||||||
ok("class Add(高一)", clsAddA.code === 0 && clsAddA.data > 0, clsAddA.msg);
|
ok("class List code=0(平台班级)", clsListAll.code === 0 && Array.isArray(clsListAll.data), clsListAll.msg);
|
||||||
const clsAddB = await api("POST", "/api/class/Add", { form: { className: clsNameB, grade: 2, studentCount: 44, headTeacher: "陈老师" } });
|
ok(
|
||||||
ok("class Add(高二)", clsAddB.code === 0 && clsAddB.data > 0, clsAddB.msg);
|
"class List 仅含高中学段(gradeId 10/11/12 之外的学段被过滤)",
|
||||||
ok("class Add 校内重名拒绝", (await api("POST", "/api/class/Add", { form: { className: clsNameA, grade: 1 } })).code !== 0);
|
clsListAll.code === 0 && (clsListAll.data || []).every(c => [1, 2, 3].includes(c.grade)),
|
||||||
ok("class Add 非法年级拒绝", (await api("POST", "/api/class/Add", { form: { className: `冒烟班X${stamp}`, grade: 9 } })).code !== 0);
|
JSON.stringify(clsListAll.data && clsListAll.data.find(c => ![1, 2, 3].includes(c.grade)))
|
||||||
|
);
|
||||||
|
const hs = (clsListAll.data || []).filter(c => c.grade === 1 || c.grade === 2);
|
||||||
|
const clsA = hs.find(c => c.grade === 1 && c.classroomId == null);
|
||||||
|
const clsB = hs.find(c => c.grade === 2 && c.classroomId == null);
|
||||||
|
ok(
|
||||||
|
"平台班级数据可用(高一/高二各有未绑定班级)",
|
||||||
|
!!clsA && !!clsB,
|
||||||
|
JSON.stringify(hs && hs.map(c => `${c.serID}:${c.className}:room${c.classroomId}`))
|
||||||
|
);
|
||||||
|
ok(
|
||||||
|
"class List 行含 serID/className/studentCount(可空)/headTeacher",
|
||||||
|
clsA && clsA.serID > 0 && !!clsA.className && (clsA.studentCount == null || Number.isInteger(clsA.studentCount)),
|
||||||
|
JSON.stringify(clsA)
|
||||||
|
);
|
||||||
const clsListG1 = await api("GET", "/api/class/List", { params: { grade: 1 } });
|
const clsListG1 = await api("GET", "/api/class/List", { params: { grade: 1 } });
|
||||||
const clsA = clsListG1.data ? clsListG1.data.find(c => c.serID === clsAddA.data) : null;
|
ok(
|
||||||
ok("class List grade=1 含新班级(未绑定 classroomId=null)", clsListG1.code === 0 && clsA && clsA.classroomId == null && clsA.headTeacher === "王老师", JSON.stringify(clsA));
|
"class List grade=1 只含高一且新班级未绑定(classroomId=null)",
|
||||||
|
clsListG1.code === 0 && (clsListG1.data || []).length > 0 && (clsListG1.data || []).every(c => c.grade === 1)
|
||||||
|
&& (clsListG1.data || []).find(c => c.serID === clsA.serID)?.classroomId == null,
|
||||||
|
JSON.stringify(clsListG1.data && clsListG1.data.length)
|
||||||
|
);
|
||||||
|
|
||||||
const bind = await api("POST", "/api/classroom/BindClass", { json: { serID: room1, classId: clsAddA.data } });
|
const bind = await api("POST", "/api/classroom/BindClass", { json: { serID: room1, classId: clsA.serID } });
|
||||||
ok("classroom BindClass 返回班级名", bind.code === 0 && bind.data && bind.data.className === clsNameA, bind.msg);
|
ok("classroom BindClass 返回班级名(平台展示名)", bind.code === 0 && bind.data && bind.data.className === clsA.className, bind.msg);
|
||||||
const bindDup = await api("POST", "/api/classroom/BindClass", { json: { serID: rooms.data.list[1].serID, classId: clsAddA.data } });
|
const bindDup = await api("POST", "/api/classroom/BindClass", { json: { serID: rooms.data.list[1].serID, classId: clsA.serID } });
|
||||||
ok("BindClass 班级已被其他教室占用拒绝", bindDup.code !== 0);
|
ok("BindClass 班级已被其他教室占用拒绝", bindDup.code !== 0);
|
||||||
ok("BindClass 缺参拒绝", (await api("POST", "/api/classroom/BindClass", { json: { serID: room1 } })).code !== 0);
|
ok("BindClass 缺参拒绝", (await api("POST", "/api/classroom/BindClass", { json: { serID: room1 } })).code !== 0);
|
||||||
|
ok(
|
||||||
|
"BindClass 班级不在平台列表拒绝",
|
||||||
|
(await api("POST", "/api/classroom/BindClass", { json: { serID: rooms.data.list[2].serID, classId: 999999999 } })).code !== 0
|
||||||
|
);
|
||||||
|
|
||||||
const roomsG1 = await api("GET", "/api/classroom/List", { params: { buildingId, grade: 1, page: 1, count: 20 } });
|
const roomsG1 = await api("GET", "/api/classroom/List", { params: { buildingId, grade: 1, page: 1, count: 20 } });
|
||||||
const boundRow = roomsG1.data ? roomsG1.data.list.find(r => r.serID === room1) : null;
|
const boundRow = roomsG1.data ? roomsG1.data.list.find(r => r.serID === room1) : null;
|
||||||
ok(
|
ok(
|
||||||
"List grade=1 含已绑教室且行含班级信息(名/人数/班主任)",
|
"List grade=1 含已绑教室且行含班级信息(名/人数/班主任同平台源)",
|
||||||
roomsG1.code === 0 && roomsG1.data.total === 6 && boundRow && boundRow.classId === clsAddA.data &&
|
roomsG1.code === 0 && roomsG1.data.total === 6 && boundRow && boundRow.classId === clsA.serID &&
|
||||||
boundRow.className === clsNameA && boundRow.studentCount === 45 && boundRow.headTeacher === "王老师",
|
boundRow.className === clsA.className && (boundRow.studentCount ?? null) === (clsA.studentCount ?? null) &&
|
||||||
|
boundRow.headTeacher === clsA.headTeacher,
|
||||||
JSON.stringify(boundRow)
|
JSON.stringify(boundRow)
|
||||||
);
|
);
|
||||||
const roomsG2 = await api("GET", "/api/classroom/List", { params: { buildingId, grade: 2, page: 1, count: 20 } });
|
const roomsG2 = await api("GET", "/api/classroom/List", { params: { buildingId, grade: 2, page: 1, count: 20 } });
|
||||||
@@ -208,14 +231,12 @@ ok(
|
|||||||
const roomsBoundOnly = await api("GET", "/api/classroom/List", { params: { buildingId, grade: 1, bindStatus: "bound", page: 1, count: 20 } });
|
const roomsBoundOnly = await api("GET", "/api/classroom/List", { params: { buildingId, grade: 1, bindStatus: "bound", page: 1, count: 20 } });
|
||||||
ok("List bindStatus=bound 只含已绑 1 间", roomsBoundOnly.code === 0 && roomsBoundOnly.data.total === 1);
|
ok("List bindStatus=bound 只含已绑 1 间", roomsBoundOnly.code === 0 && roomsBoundOnly.data.total === 1);
|
||||||
|
|
||||||
ok("class Delete 绑定中拒绝", (await api("POST", "/api/class/Delete", { form: { serid: clsAddA.data } })).code !== 0);
|
|
||||||
|
|
||||||
ok("classroom UnbindClass", (await api("POST", "/api/classroom/UnbindClass", { form: { serid: room1 } })).code === 0);
|
ok("classroom UnbindClass", (await api("POST", "/api/classroom/UnbindClass", { form: { serid: room1 } })).code === 0);
|
||||||
ok("UnbindClass 未绑定教室静默成功", (await api("POST", "/api/classroom/UnbindClass", { form: { serid: room1 } })).code === 0);
|
ok("UnbindClass 未绑定教室静默成功", (await api("POST", "/api/classroom/UnbindClass", { form: { serid: room1 } })).code === 0);
|
||||||
|
|
||||||
// 批量绑定两间 → 批量解绑 3 间(含 1 间未绑定 → skipped)
|
// 批量绑定两间 → 批量解绑 3 间(含 1 间未绑定 → skipped);班级属平台数据,解绑即可、无需清理
|
||||||
await api("POST", "/api/classroom/BindClass", { json: { serID: room1, classId: clsAddA.data } });
|
await api("POST", "/api/classroom/BindClass", { json: { serID: room1, classId: clsA.serID } });
|
||||||
await api("POST", "/api/classroom/BindClass", { json: { serID: rooms.data.list[1].serID, classId: clsAddB.data } });
|
await api("POST", "/api/classroom/BindClass", { json: { serID: rooms.data.list[1].serID, classId: clsB.serID } });
|
||||||
const bUnbind = await api("POST", "/api/classroom/BatchUnbind", { json: [room1, rooms.data.list[1].serID, rooms.data.list[2].serID] });
|
const bUnbind = await api("POST", "/api/classroom/BatchUnbind", { json: [room1, rooms.data.list[1].serID, rooms.data.list[2].serID] });
|
||||||
ok(
|
ok(
|
||||||
"BatchUnbind 3 间(updated=2, skipped=1)",
|
"BatchUnbind 3 间(updated=2, skipped=1)",
|
||||||
@@ -223,8 +244,6 @@ ok(
|
|||||||
JSON.stringify(bUnbind.data)
|
JSON.stringify(bUnbind.data)
|
||||||
);
|
);
|
||||||
ok("BatchUnbind 空集合拒绝", (await api("POST", "/api/classroom/BatchUnbind", { json: [] })).code !== 0);
|
ok("BatchUnbind 空集合拒绝", (await api("POST", "/api/classroom/BatchUnbind", { json: [] })).code !== 0);
|
||||||
ok("class Delete 解绑后可删 A", (await api("POST", "/api/class/Delete", { form: { serid: clsAddA.data } })).code === 0);
|
|
||||||
ok("class Delete 解绑后可删 B", (await api("POST", "/api/class/Delete", { form: { serid: clsAddB.data } })).code === 0);
|
|
||||||
|
|
||||||
// 菜单兜底(仅本地免 SSO 模式):绑定班级在第二位
|
// 菜单兜底(仅本地免 SSO 模式):绑定班级在第二位
|
||||||
if (!COOKIE) {
|
if (!COOKIE) {
|
||||||
|
|||||||
@@ -52,8 +52,10 @@ public class ControllerUserContextAspect {
|
|||||||
} else if (devContextEnabled) {
|
} else if (devContextEnabled) {
|
||||||
info.userId = 99999;
|
info.userId = 99999;
|
||||||
info.userName = "本地开发";
|
info.userName = "本地开发";
|
||||||
info.schId = 1;
|
// schId=3(云海中学/李晶校):班级数据已切平台接口,dev 校须在平台有班级数据才能联调绑定班级页;
|
||||||
log.debug("[UserContext] 无 SSO 会话,已使用本地开发身份(schId=1)");
|
// userId 保持 99999(平台查无此用户 → GetUserMod 走本地兜底菜单,不回真实模块树)
|
||||||
|
info.schId = 3;
|
||||||
|
log.debug("[UserContext] 无 SSO 会话,已使用本地开发身份(schId=3)");
|
||||||
}
|
}
|
||||||
info.ip = clientIp();
|
info.ip = clientIp();
|
||||||
UserContext.set(info);
|
UserContext.set(info);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.yunhai.acaclassroom.controller.api;
|
package com.yunhai.acaclassroom.controller.api;
|
||||||
|
|
||||||
import com.yunhai.acaclassroom.annotation.OperationLog;
|
|
||||||
import com.yunhai.acaclassroom.config.Config;
|
import com.yunhai.acaclassroom.config.Config;
|
||||||
import com.yunhai.acaclassroom.service.IClassService;
|
import com.yunhai.acaclassroom.service.IClassService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -10,7 +9,8 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 班级档案接口(class/,绑定班级页数据源)。
|
* 班级接口(class/,绑定班级页数据源)。
|
||||||
|
* 班级数据实时取自班级管理(bschclass)开放接口,本应用不落库、不提供增删。
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(value = Config.API_PATH + "/class/")
|
@RequestMapping(value = Config.API_PATH + "/class/")
|
||||||
@@ -19,25 +19,9 @@ public class ClassController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IClassService classService;
|
private IClassService classService;
|
||||||
|
|
||||||
/** 班级列表(全校,可按年级过滤;行含 classroomId=绑定的教室 id,未绑定为 null)。 */
|
/** 班级列表(全校高中学段,可按年级过滤;行含 classroomId=绑定的教室 id,未绑定为 null)。 */
|
||||||
@GetMapping("List")
|
@GetMapping("List")
|
||||||
public List<Map<String, Object>> list(@RequestParam(value = "grade", required = false) Integer grade) {
|
public List<Map<String, Object>> list(@RequestParam(value = "grade", required = false) Integer grade) {
|
||||||
return classService.list(grade);
|
return classService.list(grade);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("Add")
|
|
||||||
@OperationLog(module = "教室管理-绑定班级", type = "CREATE")
|
|
||||||
public Integer add(@RequestParam("className") String className,
|
|
||||||
@RequestParam("grade") Integer grade,
|
|
||||||
@RequestParam(value = "studentCount", required = false) Integer studentCount,
|
|
||||||
@RequestParam(value = "headTeacher", required = false) String headTeacher) {
|
|
||||||
return classService.add(className, grade, studentCount, headTeacher);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 删除班级(已被教室绑定时拒绝)。 */
|
|
||||||
@PostMapping("Delete")
|
|
||||||
@OperationLog(module = "教室管理-绑定班级", type = "DELETE")
|
|
||||||
public void delete(@RequestParam("serid") Integer serid) {
|
|
||||||
classService.delete(serid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
package com.yunhai.acaclassroom.entity;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
import javax.persistence.Column;
|
|
||||||
import javax.persistence.Entity;
|
|
||||||
import javax.persistence.Table;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 班级档案(绑定班级页数据源)。班名校内唯一;教室经 aca_classroom.class_id 引用。
|
|
||||||
*/
|
|
||||||
@Entity
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@Table(name = "aca_class")
|
|
||||||
public class SchoolClass extends BaseEntity {
|
|
||||||
|
|
||||||
/** 年级:1=高一 2=高二 3=高三 */
|
|
||||||
public static final int GRADE_1 = 1;
|
|
||||||
public static final int GRADE_2 = 2;
|
|
||||||
public static final int GRADE_3 = 3;
|
|
||||||
|
|
||||||
/** 班级名称(如 高一1班),校内唯一 */
|
|
||||||
@Column(name = "class_name", nullable = false, length = 50)
|
|
||||||
private String className;
|
|
||||||
|
|
||||||
/** 年级:1=高一 2=高二 3=高三 */
|
|
||||||
@Column(name = "grade", nullable = false)
|
|
||||||
private Integer grade;
|
|
||||||
|
|
||||||
/** 学生人数 */
|
|
||||||
@Column(name = "student_count", nullable = false)
|
|
||||||
private Integer studentCount;
|
|
||||||
|
|
||||||
/** 班主任姓名 */
|
|
||||||
@Column(name = "head_teacher", length = 50)
|
|
||||||
private String headTeacher;
|
|
||||||
|
|
||||||
/** 所属学校 id(多租户) */
|
|
||||||
@Column(name = "sch_id", nullable = false)
|
|
||||||
private Integer schId;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
package com.yunhai.acaclassroom.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班级管理(bschclass)开放接口 {@code /oapi/sch/getSchClassList} 响应项。
|
||||||
|
*
|
||||||
|
* <p>id 体系:{@link #getId()} 是 sch_class 物理主键;{@link #getClassId()} 是分组体系
|
||||||
|
* classId(学生树 GetStuTree 的班级节点 id 与之同源),本应用的教室绑定关系
|
||||||
|
* {@code aca_classroom.class_id} 存它——与班级服务生态(学生树/任教)同一把钥匙。</p>
|
||||||
|
*
|
||||||
|
* <p>年级口径:平台 gradeId 为平台年级字典(10=高一 11=高二 12=高三;小学 1~6、
|
||||||
|
* 初中 7~9、其他学段另有取值),本应用只有高中三个年级 Tab,
|
||||||
|
* {@link #highGrade()} 把 10/11/12 映射为 1/2/3,其余返回 null(绑定页不展示)。</p>
|
||||||
|
*/
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class PlatformSchoolClass {
|
||||||
|
|
||||||
|
/** 平台年级字典 → 本应用年级(1=高一 2=高二 3=高三)映射 */
|
||||||
|
private static final Map<Integer, Integer> HIGH_GRADE_MAP = createHighGradeMap();
|
||||||
|
|
||||||
|
private static Map<Integer, Integer> createHighGradeMap() {
|
||||||
|
Map<Integer, Integer> m = new HashMap<>();
|
||||||
|
m.put(10, 1);
|
||||||
|
m.put(11, 2);
|
||||||
|
m.put(12, 3);
|
||||||
|
return Collections.unmodifiableMap(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** sch_class 物理主键 */
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
/** 分组体系班级 id(学生树班级节点同源),绑定关系存此值 */
|
||||||
|
private Integer classId;
|
||||||
|
|
||||||
|
/** 班级编码(如 202601) */
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
/** 班级名(如 1班,不含年级前缀) */
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 平台年级字典 id(10=高一 11=高二 12=高三,小学/初中另有序列) */
|
||||||
|
private Integer gradeId;
|
||||||
|
|
||||||
|
/** 年级名(如 高一) */
|
||||||
|
private String gradeName;
|
||||||
|
|
||||||
|
private Integer schId;
|
||||||
|
|
||||||
|
private String schName;
|
||||||
|
|
||||||
|
/** 班主任(第一任课教师) */
|
||||||
|
private Integer firstTeacherId;
|
||||||
|
|
||||||
|
private String firstTeacherName;
|
||||||
|
|
||||||
|
private Integer campusId;
|
||||||
|
|
||||||
|
private Integer enrollYear;
|
||||||
|
|
||||||
|
private Boolean graduate;
|
||||||
|
|
||||||
|
/** 校内展示序 */
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
public Integer getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Integer id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getClassId() {
|
||||||
|
return classId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClassId(Integer classId) {
|
||||||
|
this.classId = classId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCode(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getGradeId() {
|
||||||
|
return gradeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGradeId(Integer gradeId) {
|
||||||
|
this.gradeId = gradeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGradeName() {
|
||||||
|
return gradeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGradeName(String gradeName) {
|
||||||
|
this.gradeName = gradeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSchId() {
|
||||||
|
return schId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSchId(Integer schId) {
|
||||||
|
this.schId = schId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSchName() {
|
||||||
|
return schName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSchName(String schName) {
|
||||||
|
this.schName = schName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getFirstTeacherId() {
|
||||||
|
return firstTeacherId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstTeacherId(Integer firstTeacherId) {
|
||||||
|
this.firstTeacherId = firstTeacherId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirstTeacherName() {
|
||||||
|
return firstTeacherName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstTeacherName(String firstTeacherName) {
|
||||||
|
this.firstTeacherName = firstTeacherName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCampusId() {
|
||||||
|
return campusId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCampusId(Integer campusId) {
|
||||||
|
this.campusId = campusId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getEnrollYear() {
|
||||||
|
return enrollYear;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnrollYear(Integer enrollYear) {
|
||||||
|
this.enrollYear = enrollYear;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getGraduate() {
|
||||||
|
return graduate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGraduate(Boolean graduate) {
|
||||||
|
this.graduate = graduate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSort() {
|
||||||
|
return sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSort(Integer sort) {
|
||||||
|
this.sort = sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 映射到本应用的年级(1/2/3);非高中学段返回 null */
|
||||||
|
public Integer highGrade() {
|
||||||
|
return gradeId == null ? null : HIGH_GRADE_MAP.get(gradeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 展示名:年级名 + 班级名(如 高一1班);年级名缺失时退化为班级名 */
|
||||||
|
public String displayName() {
|
||||||
|
String n = name == null ? "" : name.trim();
|
||||||
|
String g = gradeName == null ? "" : gradeName.trim();
|
||||||
|
if (g.isEmpty()) {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
// 年级名已是完整班级名前缀(如平台侧直接叫"高一1班")时不重复拼接
|
||||||
|
return n.startsWith(g) ? n : g + n;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.yunhai.acaclassroom.repository;
|
|
||||||
|
|
||||||
import com.yunhai.acaclassroom.entity.SchoolClass;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 班级仓储。单校班级量级为十~百级,整取后服务层过滤。
|
|
||||||
*/
|
|
||||||
public interface SchoolClassRepository extends JpaRepository<SchoolClass, Integer> {
|
|
||||||
|
|
||||||
List<SchoolClass> findBySchIdOrderByGradeAscIdAsc(Integer schId);
|
|
||||||
|
|
||||||
boolean existsBySchIdAndClassName(Integer schId, String className);
|
|
||||||
}
|
|
||||||
@@ -4,21 +4,18 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 班级档案服务(绑定班级页)。
|
* 班级服务(绑定班级页数据源)。
|
||||||
|
*
|
||||||
|
* <p>班级数据实时取自班级管理(bschclass)开放接口,不在本应用落库;
|
||||||
|
* 教室绑定关系存 {@code aca_classroom.class_id}(平台分组体系 classId)。</p>
|
||||||
*/
|
*/
|
||||||
public interface IClassService {
|
public interface IClassService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 班级列表(全校,可按年级过滤)。
|
* 班级列表(全校高中学段,可按年级过滤)。
|
||||||
*
|
*
|
||||||
* @param grade 1=高一 2=高二 3=高三(可空=全部)
|
* @param grade 1=高一 2=高二 3=高三(可空=全部)
|
||||||
* @return 行含 serID/className/grade/studentCount/headTeacher/classroomId(未绑定为 null)
|
* @return 行含 serID(=平台 classId)/className/grade/studentCount(可空)/headTeacher(可空)/classroomId(未绑定为 null)
|
||||||
*/
|
*/
|
||||||
List<Map<String, Object>> list(Integer grade);
|
List<Map<String, Object>> list(Integer grade);
|
||||||
|
|
||||||
/** 新增班级,返回 id。 */
|
|
||||||
Integer add(String className, Integer grade, Integer studentCount, String headTeacher);
|
|
||||||
|
|
||||||
/** 删除班级(已被教室绑定时拒绝)。 */
|
|
||||||
void delete(Integer id);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
package com.yunhai.acaclassroom.service.impl;
|
||||||
|
|
||||||
|
import com.yunhai.acaclassroom.model.PlatformSchoolClass;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.client.RestClientException;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班级管理(bschclass)开放接口客户端,绑定班级页班级数据的唯一来源。
|
||||||
|
*
|
||||||
|
* <p>接口口径与 AcaClassAssign 一致(配置 {@code yh.open.bschclass}):</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>班级列表:GET {@code /oapi/sch/getSchClassList?schId=&includeGraduate=false}(不含毕业班);</li>
|
||||||
|
* <li>学生树:GET {@code /oapi/SchGroup/GetStuTree?schId=&toUser=true},
|
||||||
|
* 学校→学段→年级→班级→学生五层,班级节点 id 即班级列表的 classId,
|
||||||
|
* 其下 type=2 子节点为学生,用于派生各班学生人数。</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>两个接口均做 5 分钟内存缓存(TTL,按 schId);拉取失败回退上一份缓存,
|
||||||
|
* 无缓存时降级为空(列表)/空映射(人数),不阻断页面——与同族应用降级口径一致。</p>
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class BschClassClient {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(BschClassClient.class);
|
||||||
|
|
||||||
|
/** 缓存有效期 ms */
|
||||||
|
private static final long CACHE_TTL_MS = 5 * 60 * 1000L;
|
||||||
|
|
||||||
|
/** 学生树中学生节点 type */
|
||||||
|
private static final Integer STU_NODE_TYPE = 2;
|
||||||
|
|
||||||
|
/** 班级管理服务基址 */
|
||||||
|
@Value("${yh.open.bschclass:}")
|
||||||
|
private String bschClassHost;
|
||||||
|
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
|
||||||
|
/** 班级列表缓存:schId -> {time, list} */
|
||||||
|
private final ConcurrentHashMap<Integer, ClassListCacheEntry> classListCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/** 学生人数缓存:schId -> {time, classId -> 人数} */
|
||||||
|
private final ConcurrentHashMap<Integer, StuCountCacheEntry> stuCountCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private static class ClassListCacheEntry {
|
||||||
|
final long time;
|
||||||
|
final List<PlatformSchoolClass> list;
|
||||||
|
|
||||||
|
ClassListCacheEntry(long time, List<PlatformSchoolClass> list) {
|
||||||
|
this.time = time;
|
||||||
|
this.list = list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class StuCountCacheEntry {
|
||||||
|
final long time;
|
||||||
|
final Map<Integer, Integer> counts;
|
||||||
|
|
||||||
|
StuCountCacheEntry(long time, Map<Integer, Integer> counts) {
|
||||||
|
this.time = time;
|
||||||
|
this.counts = counts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BschClassClient() {
|
||||||
|
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
||||||
|
factory.setConnectTimeout(5000);
|
||||||
|
factory.setReadTimeout(10000);
|
||||||
|
this.restTemplate = new RestTemplate(factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 学校班级列表(不含毕业班,5 分钟缓存;不可达回退旧缓存,无缓存降级空列表)。 */
|
||||||
|
public List<PlatformSchoolClass> classList(int schId) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
ClassListCacheEntry cached = classListCache.get(schId);
|
||||||
|
if (cached != null && now - cached.time < CACHE_TTL_MS) {
|
||||||
|
return cached.list;
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
cached = classListCache.get(schId);
|
||||||
|
if (cached != null && now - cached.time < CACHE_TTL_MS) {
|
||||||
|
return cached.list;
|
||||||
|
}
|
||||||
|
List<PlatformSchoolClass> list = fetchClassList(schId);
|
||||||
|
if (list == null) {
|
||||||
|
return cached != null ? cached.list : Collections.emptyList();
|
||||||
|
}
|
||||||
|
classListCache.put(schId, new ClassListCacheEntry(now, list));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 班级 id → 学生人数(学生树派生,5 分钟缓存;树不可达回退旧缓存,无缓存降级空映射)。 */
|
||||||
|
public Map<Integer, Integer> studentCounts(int schId) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
StuCountCacheEntry cached = stuCountCache.get(schId);
|
||||||
|
if (cached != null && now - cached.time < CACHE_TTL_MS) {
|
||||||
|
return cached.counts;
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
cached = stuCountCache.get(schId);
|
||||||
|
if (cached != null && now - cached.time < CACHE_TTL_MS) {
|
||||||
|
return cached.counts;
|
||||||
|
}
|
||||||
|
Map<Integer, Integer> counts = fetchStudentCounts(schId);
|
||||||
|
if (counts == null) {
|
||||||
|
return cached != null ? cached.counts : Collections.emptyMap();
|
||||||
|
}
|
||||||
|
stuCountCache.put(schId, new StuCountCacheEntry(now, counts));
|
||||||
|
return counts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 按班级 id 找单个班级(走列表缓存);不存在返回 null。 */
|
||||||
|
public PlatformSchoolClass find(int schId, Integer classId) {
|
||||||
|
if (classId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return classList(schId).stream()
|
||||||
|
.filter(c -> classId.equals(c.getClassId()))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<PlatformSchoolClass> fetchClassList(int schId) {
|
||||||
|
if (!StringUtils.hasText(bschClassHost)) {
|
||||||
|
log.warn("[fetchClassList] 班级管理接口未配置(yh.open.bschclass)");
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
String url = bschClassHost + "/oapi/sch/getSchClassList?schId=" + schId + "&includeGraduate=false";
|
||||||
|
try {
|
||||||
|
ResponseEntity<List<PlatformSchoolClass>> resp = restTemplate.exchange(
|
||||||
|
url, HttpMethod.GET, null,
|
||||||
|
new ParameterizedTypeReference<List<PlatformSchoolClass>>() {
|
||||||
|
});
|
||||||
|
List<PlatformSchoolClass> body = resp.getBody();
|
||||||
|
log.info("[fetchClassList] schId={}, 班级 {} 个", schId, body == null ? 0 : body.size());
|
||||||
|
return body != null ? body : Collections.emptyList();
|
||||||
|
} catch (RestClientException e) {
|
||||||
|
log.error("[fetchClassList] 请求班级列表失败, schId={}, url={}", schId, url, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 拉学生树并按班级节点计数;不可达返回 null(区分"树为空")。 */
|
||||||
|
private Map<Integer, Integer> fetchStudentCounts(int schId) {
|
||||||
|
if (!StringUtils.hasText(bschClassHost)) {
|
||||||
|
log.warn("[fetchStudentCounts] 班级管理接口未配置(yh.open.bschclass)");
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
String url = bschClassHost + "/oapi/SchGroup/GetStuTree?schId=" + schId + "&toUser=true";
|
||||||
|
try {
|
||||||
|
ResponseEntity<List<Map<String, Object>>> resp = restTemplate.exchange(
|
||||||
|
url, HttpMethod.GET, null,
|
||||||
|
new ParameterizedTypeReference<List<Map<String, Object>>>() {
|
||||||
|
});
|
||||||
|
List<Map<String, Object>> body = resp.getBody();
|
||||||
|
Map<Integer, Integer> counts = new LinkedHashMap<>();
|
||||||
|
if (body != null) {
|
||||||
|
// 固定层级 学校→学段→年级→班级→学生(与 AcaClassAssign buildIndex 同一口径):
|
||||||
|
// 第 4 层班级节点 id 即班级列表 classId,其子节点 type=2 的为学生
|
||||||
|
for (Map<String, Object> school : orEmpty(body)) {
|
||||||
|
for (Map<String, Object> system : childrenOf(school)) {
|
||||||
|
for (Map<String, Object> grade : childrenOf(system)) {
|
||||||
|
for (Map<String, Object> clazz : childrenOf(grade)) {
|
||||||
|
Integer classId = asInt(clazz.get("id"));
|
||||||
|
if (classId != null) {
|
||||||
|
counts.put(classId, (int) childrenOf(clazz).stream()
|
||||||
|
.filter(stu -> stu != null && STU_NODE_TYPE.equals(asInt(stu.get("type"))))
|
||||||
|
.count());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info("[fetchStudentCounts] schId={}, 班级人数 {} 项", schId, counts.size());
|
||||||
|
return counts;
|
||||||
|
} catch (RestClientException e) {
|
||||||
|
log.error("[fetchStudentCounts] 请求学生树失败, schId={}, url={}", schId, url, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private List<Map<String, Object>> childrenOf(Map<String, Object> node) {
|
||||||
|
if (node == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
Object children = node.get("children");
|
||||||
|
return children instanceof List ? (List<Map<String, Object>>) children : Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Map<String, Object>> orEmpty(List<Map<String, Object>> list) {
|
||||||
|
return list != null ? list : Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer asInt(Object v) {
|
||||||
|
if (v == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (v instanceof Number) {
|
||||||
|
return ((Number) v).intValue();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Integer.valueOf(String.valueOf(v));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,28 +1,28 @@
|
|||||||
package com.yunhai.acaclassroom.service.impl;
|
package com.yunhai.acaclassroom.service.impl;
|
||||||
|
|
||||||
import com.yunhai.acaclassroom.entity.Classroom;
|
import com.yunhai.acaclassroom.model.PlatformSchoolClass;
|
||||||
import com.yunhai.acaclassroom.entity.SchoolClass;
|
|
||||||
import com.yunhai.acaclassroom.repository.ClassroomRepository;
|
import com.yunhai.acaclassroom.repository.ClassroomRepository;
|
||||||
import com.yunhai.acaclassroom.repository.SchoolClassRepository;
|
|
||||||
import com.yunhai.acaclassroom.service.IClassService;
|
import com.yunhai.acaclassroom.service.IClassService;
|
||||||
import com.yunhai.acaclassroom.utils.UserContext;
|
import com.yunhai.acaclassroom.utils.UserContext;
|
||||||
import com.yunhai.platform.apistd.BizException;
|
|
||||||
import com.yunhai.platform.utils.exception.ErrorCode;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 班级档案服务实现。班级数据量小(单校十~百级),整取后内存过滤。
|
* 班级服务实现(绑定班级页数据源)。
|
||||||
|
*
|
||||||
|
* <p>班级档案不在本应用落库:班级数据实时取自班级管理(bschclass)开放接口
|
||||||
|
* {@link BschClassClient}(5 分钟缓存、失败降级),绑定关系仍存本应用
|
||||||
|
* {@code aca_classroom.class_id}(存平台分组体系 classId)。此前本地档案表
|
||||||
|
* {@code aca_class} 及 class/Add·Delete 接口已随数据源切换移除。</p>
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class ClassService implements IClassService {
|
public class ClassService implements IClassService {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SchoolClassRepository classRepository;
|
private BschClassClient bschClassClient;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ClassroomRepository classroomRepository;
|
private ClassroomRepository classroomRepository;
|
||||||
@@ -33,72 +33,35 @@ public class ClassService implements IClassService {
|
|||||||
if (schId == null) {
|
if (schId == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
List<SchoolClass> classes = classRepository.findBySchIdOrderByGradeAscIdAsc(schId);
|
// 仅保留高中学段(gradeId 10/11/12 → 1/2/3),其余学段不进绑定页
|
||||||
|
List<PlatformSchoolClass> classes = bschClassClient.classList(schId).stream()
|
||||||
|
.filter(c -> c.highGrade() != null && c.getClassId() != null)
|
||||||
|
.sorted(Comparator
|
||||||
|
.comparingInt((PlatformSchoolClass c) -> c.highGrade())
|
||||||
|
.thenComparing(c -> c.getSort() == null ? Integer.MAX_VALUE : c.getSort())
|
||||||
|
.thenComparing(c -> c.getId() == null ? Integer.MAX_VALUE : c.getId()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
if (grade != null) {
|
if (grade != null) {
|
||||||
classes = classes.stream().filter(c -> grade.equals(c.getGrade())).collect(Collectors.toList());
|
classes = classes.stream().filter(c -> grade.equals(c.highGrade())).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
// 班级 → 绑定的教室 id(绑定状态下拉过滤/行回显用)
|
// 班级 → 绑定的教室 id(绑定状态下拉过滤/行回显用)
|
||||||
List<Integer> ids = classes.stream().map(SchoolClass::getId).collect(Collectors.toList());
|
List<Integer> ids = classes.stream().map(PlatformSchoolClass::getClassId).collect(Collectors.toList());
|
||||||
Map<Integer, Integer> boundRooms = ids.isEmpty()
|
Map<Integer, Integer> boundRooms = ids.isEmpty()
|
||||||
? Collections.emptyMap()
|
? Collections.emptyMap()
|
||||||
: classroomRepository.findBySchIdAndClassIdIn(schId, ids).stream()
|
: classroomRepository.findBySchIdAndClassIdIn(schId, ids).stream()
|
||||||
.collect(Collectors.toMap(Classroom::getClassId, Classroom::getId, (a, b) -> a));
|
.collect(Collectors.toMap(com.yunhai.acaclassroom.entity.Classroom::getClassId,
|
||||||
|
com.yunhai.acaclassroom.entity.Classroom::getId, (a, b) -> a));
|
||||||
|
// 学生人数来自学生树(树不可达时为空映射 → 前端隐藏该段)
|
||||||
|
Map<Integer, Integer> stuCounts = bschClassClient.studentCounts(schId);
|
||||||
return classes.stream().map(c -> {
|
return classes.stream().map(c -> {
|
||||||
Map<String, Object> vo = new LinkedHashMap<>();
|
Map<String, Object> vo = new LinkedHashMap<>();
|
||||||
vo.put("serID", c.getId());
|
vo.put("serID", c.getClassId());
|
||||||
vo.put("className", c.getClassName());
|
vo.put("className", c.displayName());
|
||||||
vo.put("grade", c.getGrade());
|
vo.put("grade", c.highGrade());
|
||||||
vo.put("studentCount", c.getStudentCount());
|
vo.put("studentCount", stuCounts.get(c.getClassId()));
|
||||||
vo.put("headTeacher", c.getHeadTeacher());
|
vo.put("headTeacher", c.getFirstTeacherName());
|
||||||
vo.put("classroomId", boundRooms.get(c.getId()));
|
vo.put("classroomId", boundRooms.get(c.getClassId()));
|
||||||
return vo;
|
return vo;
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public Integer add(String className, Integer grade, Integer studentCount, String headTeacher) {
|
|
||||||
Integer schId = UserContext.getSchId();
|
|
||||||
if (schId == null) {
|
|
||||||
throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "未能获取当前学校信息,请重新登录");
|
|
||||||
}
|
|
||||||
String name = className != null ? className.trim() : "";
|
|
||||||
if (name.isEmpty() || name.length() > 50) {
|
|
||||||
throw new BizException(ErrorCode.PARAM_ERROR, "请输入 50 字以内的班级名称");
|
|
||||||
}
|
|
||||||
if (grade == null || grade < SchoolClass.GRADE_1 || grade > SchoolClass.GRADE_3) {
|
|
||||||
throw new BizException(ErrorCode.PARAM_ERROR, "年级只支持 1=高一 2=高二 3=高三");
|
|
||||||
}
|
|
||||||
if (classRepository.existsBySchIdAndClassName(schId, name)) {
|
|
||||||
throw new BizException(ErrorCode.PARAM_ERROR, "班级名称已存在");
|
|
||||||
}
|
|
||||||
int count = studentCount == null ? 0 : studentCount;
|
|
||||||
if (count < 0 || count > 10000) {
|
|
||||||
throw new BizException(ErrorCode.PARAM_ERROR, "学生人数不正确");
|
|
||||||
}
|
|
||||||
SchoolClass c = new SchoolClass();
|
|
||||||
c.setClassName(name);
|
|
||||||
c.setGrade(grade);
|
|
||||||
c.setStudentCount(count);
|
|
||||||
c.setHeadTeacher(headTeacher != null && !headTeacher.trim().isEmpty() ? headTeacher.trim() : null);
|
|
||||||
c.setSchId(schId);
|
|
||||||
return classRepository.save(c).getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void delete(Integer id) {
|
|
||||||
Integer schId = UserContext.getSchId();
|
|
||||||
if (schId == null) {
|
|
||||||
throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "未能获取当前学校信息,请重新登录");
|
|
||||||
}
|
|
||||||
SchoolClass c = classRepository.findById(id).orElse(null);
|
|
||||||
if (c == null || !schId.equals(c.getSchId())) {
|
|
||||||
throw new BizException(ErrorCode.PARAM_ERROR, "班级不存在或无权操作");
|
|
||||||
}
|
|
||||||
if (classroomRepository.existsBySchIdAndClassId(schId, id)) {
|
|
||||||
throw new BizException(ErrorCode.PARAM_ERROR, "该班级已绑定教室,请先解绑再删除");
|
|
||||||
}
|
|
||||||
classRepository.delete(c);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.yunhai.acaclassroom.entity.ClassroomType;
|
|||||||
import com.yunhai.acaclassroom.model.ClassroomBatchAddRequest;
|
import com.yunhai.acaclassroom.model.ClassroomBatchAddRequest;
|
||||||
import com.yunhai.acaclassroom.model.ClassroomBatchEditRequest;
|
import com.yunhai.acaclassroom.model.ClassroomBatchEditRequest;
|
||||||
import com.yunhai.acaclassroom.model.ClassroomEditRequest;
|
import com.yunhai.acaclassroom.model.ClassroomEditRequest;
|
||||||
|
import com.yunhai.acaclassroom.model.PlatformSchoolClass;
|
||||||
import com.yunhai.acaclassroom.repository.ClassroomRepository;
|
import com.yunhai.acaclassroom.repository.ClassroomRepository;
|
||||||
import com.yunhai.acaclassroom.repository.ClassroomTypeRepository;
|
import com.yunhai.acaclassroom.repository.ClassroomTypeRepository;
|
||||||
import com.yunhai.acaclassroom.repository.EquipmentRepository;import com.yunhai.acaclassroom.service.IClassroomService;
|
import com.yunhai.acaclassroom.repository.EquipmentRepository;import com.yunhai.acaclassroom.service.IClassroomService;
|
||||||
@@ -49,7 +50,7 @@ public class ClassroomService implements IClassroomService {
|
|||||||
private com.yunhai.acaclassroom.repository.BuildingRepository buildingRepository;
|
private com.yunhai.acaclassroom.repository.BuildingRepository buildingRepository;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private com.yunhai.acaclassroom.repository.SchoolClassRepository classRepository;
|
private BschClassClient bschClassClient;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private BuildingService buildingService;
|
private BuildingService buildingService;
|
||||||
@@ -93,10 +94,11 @@ public class ClassroomService implements IClassroomService {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 班级映射:绑定状态过滤 / 年级 Tab 过滤 / 行内班级信息展示共用
|
// 班级映射(平台班级,键=分组体系 classId):绑定状态过滤 / 年级 Tab 过滤 / 行内班级信息展示共用
|
||||||
Map<Integer, com.yunhai.acaclassroom.entity.SchoolClass> classMap = classRepository
|
Map<Integer, PlatformSchoolClass> classMap = bschClassClient.classList(schId).stream()
|
||||||
.findBySchIdOrderByGradeAscIdAsc(schId).stream()
|
.filter(c -> c.getClassId() != null)
|
||||||
.collect(Collectors.toMap(com.yunhai.acaclassroom.entity.SchoolClass::getId, c -> c, (a, b) -> a));
|
.collect(Collectors.toMap(PlatformSchoolClass::getClassId, c -> c, (a, b) -> a));
|
||||||
|
Map<Integer, Integer> stuCounts = bschClassClient.studentCounts(schId);
|
||||||
if ("bound".equals(bindStatus)) {
|
if ("bound".equals(bindStatus)) {
|
||||||
rooms = rooms.stream().filter(r -> r.getClassId() != null).collect(Collectors.toList());
|
rooms = rooms.stream().filter(r -> r.getClassId() != null).collect(Collectors.toList());
|
||||||
} else if ("unbound".equals(bindStatus)) {
|
} else if ("unbound".equals(bindStatus)) {
|
||||||
@@ -108,8 +110,8 @@ public class ClassroomService implements IClassroomService {
|
|||||||
if (r.getClassId() == null) {
|
if (r.getClassId() == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
com.yunhai.acaclassroom.entity.SchoolClass c = classMap.get(r.getClassId());
|
PlatformSchoolClass c = classMap.get(r.getClassId());
|
||||||
return c != null && grade.equals(c.getGrade());
|
return c != null && grade.equals(c.highGrade());
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,11 +165,11 @@ public class ClassroomService implements IClassroomService {
|
|||||||
Map<String, Object> vo = toVo(r, typeNames);
|
Map<String, Object> vo = toVo(r, typeNames);
|
||||||
vo.put("campusName", campusNames.getOrDefault(r.getCampusId(), ""));
|
vo.put("campusName", campusNames.getOrDefault(r.getCampusId(), ""));
|
||||||
vo.put("buildingName", buildingNames.getOrDefault(r.getBuildingId(), ""));
|
vo.put("buildingName", buildingNames.getOrDefault(r.getBuildingId(), ""));
|
||||||
com.yunhai.acaclassroom.entity.SchoolClass bound = r.getClassId() != null ? classMap.get(r.getClassId()) : null;
|
PlatformSchoolClass bound = r.getClassId() != null ? classMap.get(r.getClassId()) : null;
|
||||||
vo.put("classId", r.getClassId());
|
vo.put("classId", r.getClassId());
|
||||||
vo.put("className", bound != null ? bound.getClassName() : null);
|
vo.put("className", bound != null ? bound.displayName() : null);
|
||||||
vo.put("studentCount", bound != null ? bound.getStudentCount() : null);
|
vo.put("studentCount", bound != null ? stuCounts.get(r.getClassId()) : null);
|
||||||
vo.put("headTeacher", bound != null ? bound.getHeadTeacher() : null);
|
vo.put("headTeacher", bound != null ? bound.getFirstTeacherName() : null);
|
||||||
Map<Integer, Integer> eqQty = roomEquipmentQty.getOrDefault(r.getId(), Collections.emptyMap());
|
Map<Integer, Integer> eqQty = roomEquipmentQty.getOrDefault(r.getId(), Collections.emptyMap());
|
||||||
vo.put("equipmentIds", new ArrayList<>(eqQty.keySet()));
|
vo.put("equipmentIds", new ArrayList<>(eqQty.keySet()));
|
||||||
vo.put("equipmentQtys", eqQty);
|
vo.put("equipmentQtys", eqQty);
|
||||||
@@ -473,8 +475,9 @@ public class ClassroomService implements IClassroomService {
|
|||||||
throw new BizException(ErrorCode.PARAM_ERROR, "教室ID与班级ID不得为空");
|
throw new BizException(ErrorCode.PARAM_ERROR, "教室ID与班级ID不得为空");
|
||||||
}
|
}
|
||||||
Classroom r = loadOwned(serID);
|
Classroom r = loadOwned(serID);
|
||||||
com.yunhai.acaclassroom.entity.SchoolClass c = classRepository.findById(classId).orElse(null);
|
// 班级以班级管理平台数据为准:须存在于本校班级列表(班级不在本应用落库)
|
||||||
if (c == null || !r.getSchId().equals(c.getSchId())) {
|
PlatformSchoolClass c = bschClassClient.find(r.getSchId(), classId);
|
||||||
|
if (c == null) {
|
||||||
throw new BizException(ErrorCode.PARAM_ERROR, "班级不存在或无权操作");
|
throw new BizException(ErrorCode.PARAM_ERROR, "班级不存在或无权操作");
|
||||||
}
|
}
|
||||||
// 一个班级同时只能绑一间教室
|
// 一个班级同时只能绑一间教室
|
||||||
@@ -488,7 +491,7 @@ public class ClassroomService implements IClassroomService {
|
|||||||
classroomRepository.save(r);
|
classroomRepository.save(r);
|
||||||
Map<String, Object> result = new LinkedHashMap<>();
|
Map<String, Object> result = new LinkedHashMap<>();
|
||||||
result.put("classId", classId);
|
result.put("classId", classId);
|
||||||
result.put("className", c.getClassName());
|
result.put("className", c.displayName());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
-- 迁移:移除教室容纳人数(capacity)。
|
||||||
|
-- 实体 Classroom 已删除该字段;JPA ddl-auto=update 只加列不删列,需手工执行本脚本。
|
||||||
|
-- 本地:mysql -uroot -p123123 < acaclassroom_migration_drop_capacity.sql
|
||||||
|
USE aca_classroom;
|
||||||
|
|
||||||
|
ALTER TABLE aca_classroom DROP COLUMN capacity;
|
||||||
Reference in New Issue
Block a user