Compare commits
31
Commits
875f96ac3c
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e063e67822 | ||
|
|
e0c9e14ab9 | ||
|
|
3fa52f21af | ||
|
|
a0c6a27f55 | ||
|
|
cee411472a | ||
|
|
a669bb67ec | ||
|
|
064c855a55 | ||
|
|
d633609306 | ||
|
|
1c19e3d2ef | ||
|
|
aecab2fd91 | ||
|
|
9f3b49c30a | ||
|
|
3e18856c0c | ||
|
|
43484a8936 | ||
|
|
8a61c77b12 | ||
|
|
0b1d8a3ecf | ||
|
|
700d6526b3 | ||
|
|
dde2a147b0 | ||
|
|
0462c772e4 | ||
|
|
086e0bbf39 | ||
|
|
b9782faeae | ||
|
|
2d4f3db219 | ||
|
|
2cf8334d74 | ||
|
|
d9604b3798 | ||
|
|
1b45924003 | ||
|
|
f73d9da730 | ||
|
|
eb734c5fea | ||
|
|
076524218f | ||
|
|
8a54bd7dc5 | ||
|
|
d1a748c0ff | ||
|
|
2aefd1175b | ||
|
|
391fab5c11 |
+25
-10
@@ -24,8 +24,17 @@ jobs:
|
|||||||
image: docker:24
|
image: docker:24
|
||||||
options: --network infra-infra-net
|
options: --network infra-infra-net
|
||||||
steps:
|
steps:
|
||||||
- name: 安装 git / curl(alpine)
|
# dl-cdn 在 CI 容器内被出口防火墙拒(apk 报 Permission denied,索引拉不下来即 curl no such package),
|
||||||
run: apk add --no-cache git curl
|
# 切国内镜像源安装;aliyun 不通再退 ustc,最后兜底官方源
|
||||||
|
- name: 安装 git / curl(alpine,国内镜像源)
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
for m in mirrors.aliyun.com mirrors.ustc.edu.cn dl-cdn.alpinelinux.org; do
|
||||||
|
sed -i "s#https://[^/]*#https://$m#" /etc/apk/repositories
|
||||||
|
echo ">> trying mirror: $m"
|
||||||
|
if apk add --no-cache git curl; then break; fi
|
||||||
|
done
|
||||||
|
git --version && curl --version | head -1
|
||||||
|
|
||||||
# 内网 Gitea 无法访问 GitHub 拉 actions/checkout,手动 clone 本仓库
|
# 内网 Gitea 无法访问 GitHub 拉 actions/checkout,手动 clone 本仓库
|
||||||
- name: 拉取源码(内网无 GitHub,手动 clone 本仓库)
|
- name: 拉取源码(内网无 GitHub,手动 clone 本仓库)
|
||||||
@@ -55,18 +64,24 @@ jobs:
|
|||||||
- name: 构建 jar(maven 容器,复用宿主 m2 缓存)
|
- name: 构建 jar(maven 容器,复用宿主 m2 缓存)
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
docker rm -f mavenbuild 2>/dev/null || true
|
# 构建容器名必须全局唯一:runner 挂的是宿主 docker.sock,容器名在整个 daemon 上共享
|
||||||
docker run -d --name mavenbuild \
|
# 命名空间。固定名会在多仓/多条 workflow 并发时互相 docker rm -f 掉对方正在构建的
|
||||||
|
# 容器,表现为无编译报错的 exit 137(SIGKILL,2026-10-21 实测复现)。
|
||||||
|
# GITHUB_RUN_ID 实例级唯一,再拼时间戳兜底。
|
||||||
|
BUILD_CONTAINER="mavenbuild-${GITHUB_RUN_ID:-0}-$(date +%s)"
|
||||||
|
echo "BUILD_CONTAINER=${BUILD_CONTAINER}"
|
||||||
|
docker rm -f "$BUILD_CONTAINER" 2>/dev/null || true
|
||||||
|
docker run -d --name "$BUILD_CONTAINER" \
|
||||||
--network infra-infra-net \
|
--network infra-infra-net \
|
||||||
-v /opt/docker/projman-m2:/root/.m2 \
|
-v /opt/docker/projman-m2:/root/.m2 \
|
||||||
-w /build maven:3.6.3-jdk-8 sleep 3600
|
-w /build maven:3.6.3-jdk-8 sleep 3600
|
||||||
docker cp "${PWD}/." mavenbuild:/build/
|
docker cp "${PWD}/." "$BUILD_CONTAINER":/build/
|
||||||
docker exec mavenbuild sh -c "printf '%s' '<settings><profiles><profile><id>nexus-repo</id><repositories><repository><id>nexus-internal</id><url>http://nexus:8081/repository/maven-public/</url><releases><enabled>true</enabled></releases><snapshots><enabled>true</enabled><updatePolicy>always</updatePolicy></snapshots></repository></repositories></profile></profiles><activeProfiles><activeProfile>nexus-repo</activeProfile></activeProfiles></settings>' > /root/.m2/settings.xml"
|
docker exec "$BUILD_CONTAINER" sh -c "printf '%s' '<settings><profiles><profile><id>nexus-repo</id><repositories><repository><id>nexus-internal</id><url>http://nexus:8081/repository/maven-public/</url><releases><enabled>true</enabled></releases><snapshots><enabled>true</enabled><updatePolicy>always</updatePolicy></snapshots></repository></repositories></profile></profiles><activeProfiles><activeProfile>nexus-repo</activeProfile></activeProfiles></settings>' > /root/.m2/settings.xml"
|
||||||
docker exec mavenbuild sh -c 'cd /build && mvn -B -DskipTests clean package'
|
docker exec "$BUILD_CONTAINER" sh -c 'cd /build && mvn -B -DskipTests clean package'
|
||||||
docker exec mavenbuild sh -c 'cd /build/target && cp *.jar app.jar'
|
docker exec "$BUILD_CONTAINER" sh -c 'cd /build/target && cp *.jar app.jar'
|
||||||
mkdir -p "${PWD}/target"
|
mkdir -p "${PWD}/target"
|
||||||
docker cp mavenbuild:/build/target/app.jar "${PWD}/target/app.jar"
|
docker cp "$BUILD_CONTAINER":/build/target/app.jar "${PWD}/target/app.jar"
|
||||||
docker rm -f mavenbuild
|
docker rm -f "$BUILD_CONTAINER"
|
||||||
|
|
||||||
- name: 构建并推送镜像
|
- name: 构建并推送镜像
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
## 根因
|
||||||
|
|
||||||
|
`TeainfoController.convertToTeaInfoVO`(TeainfoController.java:84-86)检查 `teacher.getGroupList()`,但它是一个 `@Transient` 字段,而 `getSchTeacherList` 查询链路(SchTeacherCacheService.java:36-44)只查 `sch_teacher` 本表、从不填充它。因此**每个教师都在第 86 行 `return null`**,`stream().map()` 不过滤 null,接口最终返回 `200 OK + [null, null, ...]`。`GetTeainfo` 和 `GetArrTeainfo` 共用同一转换方法,有一模一样的 bug。
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
### 改动 1:TeainfoController.java(修复 null 元素)
|
||||||
|
|
||||||
|
1. **注入 `SchTeacherGroupRepository`**(新增 `@Autowired` 字段 + import:`SchTeacherGroupRepository`、`java.util.Map`、`java.util.Collections`、`java.util.Objects`)。
|
||||||
|
|
||||||
|
2. **新增私有方法 `fillGroupList(List<SchTeacher> teachers, Integer schId)`**:批量查询组关系并回填到每个教师的 `@Transient groupList` 字段,复用项目既有模式(SchTeacherService.java:96-98 的 `findByTeaIdIn` + `groupingBy`):
|
||||||
|
|
||||||
|
```java
|
||||||
|
private void fillGroupList(List<SchTeacher> teachers, Integer schId) {
|
||||||
|
if (teachers == null || teachers.isEmpty()) return;
|
||||||
|
List<Integer> teaIds = teachers.stream()
|
||||||
|
.map(SchTeacher::getTeaId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (teaIds.isEmpty()) return;
|
||||||
|
List<SchTeacherGroup> relations = (schId != null)
|
||||||
|
? schTeacherGroupRepository.findByTeaIdInAndSchId(teaIds, schId)
|
||||||
|
: schTeacherGroupRepository.findByTeaIdIn(teaIds);
|
||||||
|
Map<Integer, List<SchTeacherGroup>> groupMap = relations.stream()
|
||||||
|
.filter(g -> g.getTeaId() != null)
|
||||||
|
.collect(Collectors.groupingBy(SchTeacherGroup::getTeaId));
|
||||||
|
teachers.forEach(t -> t.setGroupList(groupMap.getOrDefault(t.getTeaId(), Collections.emptyList())));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **三个接口调用处**:
|
||||||
|
- `getSchTeainfo`(:143-147):查询后调用 `fillGroupList(teachers, resolvedSchId)`(按学校过滤组关系,保证 GroupID/SchSerID 取的是本校分组),流上加 `.filter(Objects::nonNull)` 兜底。
|
||||||
|
- `getArrTeainfo`(:107-110):调用 `fillGroupList(teachers, null)`(无学校上下文,按教师ID查全部分组),流上加 `.filter(Objects::nonNull)`。
|
||||||
|
- `getTeainfo`(:52-55):`Optional.map` 内对单个教师调用 `fillGroupList(Collections.singletonList(teacher), null)`(Java 8,不用 `List.of`)。
|
||||||
|
|
||||||
|
### 改动 2:TeaInfoVO.java(锁定 JSON 键名)
|
||||||
|
|
||||||
|
每个字段加 `@JsonProperty("原始字段名")`(如 `@JsonProperty("SchSerID") private String SchSerID;`),与同包 SchInfoVO 的写法一致,确保输出键名为 `SchSerID`/`TeaIDCard`/`GroupID` 等原始大写形式,而不是 Jackson 默认推导的 `schSerID`/`teaIDCard`。新增 import `com.fasterxml.jackson.annotation.JsonProperty`。
|
||||||
|
|
||||||
|
## 保持现状(不在本次范围)
|
||||||
|
|
||||||
|
- `TeaNo` 映射的是 `sort` 排序号、其余 12 个字段(SchName、Photo 等)从不赋值恒为 null —— 属于字段映射契约问题,另行确认。
|
||||||
|
- `findTeaIdsBySchId` 不过滤 `transfer_status`(已转出教师也会出现在列表中)—— 与其他接口口径不一致,保持现状。
|
||||||
|
- 文件中历史遗留的乱码注释不动;新增代码 UTF-8 + CRLF 与文件一致。
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
- `mvn -q compile` 编译通过。
|
||||||
|
- 如有条件,本地起服务后请求 `/oapi/OATea/GetSchTeainfo?schId=xxx`,确认返回教师数组(元素为对象而非 null,且键名为原始大写形式)。
|
||||||
@@ -204,6 +204,21 @@
|
|||||||
<artifactId>yh-api-std</artifactId>
|
<artifactId>yh-api-std</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 前端运行时配置端点 GET /api/env-config(免登录,starter 自带 SSO 旁路过滤器):
|
||||||
|
数据源默认 FileEnvConfigSource 读 /app/config/env-config-{target}.json(projman 逐文件 bind mount)。
|
||||||
|
注意本端不在聚合包上,聚合里的 env-config 不会传递进来,必须显式引入 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.yunhai.platform</groupId>
|
||||||
|
<artifactId>yh-env-config-spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- 外观偏好同源读端点 GET /api/appearance(三态 proxy/local/off,默认 proxy 经 YschOpenCall
|
||||||
|
代理 UserHome /oapi/appearance)。平台件刻意不进聚合包(防本地同名控制器双注册),必须显式引入。
|
||||||
|
生效条件=@ConditionalOnBean(YschOpenCall),本端 yh.open.base-host 已配即满足 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.yunhai.platform</groupId>
|
||||||
|
<artifactId>yh-appearance-spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- CAS 客户端(浏览器 SSO 重定向 / ticket 校验 / getRemoteUser 包装)。
|
<!-- CAS 客户端(浏览器 SSO 重定向 / ticket 校验 / getRemoteUser 包装)。
|
||||||
版本 3.5.1 已在本地 .m2 仓库。 -->
|
版本 3.5.1 已在本地 .m2 仓库。 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ import java.util.Optional;
|
|||||||
@Repository
|
@Repository
|
||||||
public interface SchStudentRepository extends JpaRepository<SchStudent, Integer>, JpaSpecificationExecutor<SchStudent> {
|
public interface SchStudentRepository extends JpaRepository<SchStudent, Integer>, JpaSpecificationExecutor<SchStudent> {
|
||||||
|
|
||||||
Optional<SchStudent> findByStuId(Integer stuId);
|
/**
|
||||||
|
* stu_id 是用户中心用户ID(非本表主键),学生在多所学校各有一条就学记录,
|
||||||
|
* 同一 stu_id 可能存在多行,必须返回 List;返回 Optional 会在多行时抛 NonUniqueResultException
|
||||||
|
*/
|
||||||
|
List<SchStudent> findByStuId(Integer stuId);
|
||||||
|
|
||||||
List<SchStudent> findByStuIdIn(List<Integer> stuIds);
|
List<SchStudent> findByStuIdIn(List<Integer> stuIds);
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ public interface SchTeacherRepository extends JpaRepository<SchTeacher, Integer>
|
|||||||
|
|
||||||
List<SchTeacher> findByTeaIdIn(List<Integer> teaIds);
|
List<SchTeacher> findByTeaIdIn(List<Integer> teaIds);
|
||||||
|
|
||||||
Optional<SchTeacher> findByTeaId(Integer teaId);
|
/**
|
||||||
|
* tea_id 是用户中心用户ID(非本表主键),教师在多所学校各有一条记录,
|
||||||
|
* 同一 tea_id 可能存在多行,必须返回 List;返回 Optional 会在多行时抛 NonUniqueResultException
|
||||||
|
*/
|
||||||
|
List<SchTeacher> findByTeaId(Integer teaId);
|
||||||
|
|
||||||
|
|
||||||
@Query(value = "SELECT DISTINCT t.* FROM sch_teacher t " +
|
@Query(value = "SELECT DISTINCT t.* FROM sch_teacher t " +
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
public class LogController extends BaseController {
|
public class LogController extends BaseController {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ILogService logService; // 使用接口1
|
private ILogService logService; // 使用接口
|
||||||
|
|
||||||
@GetMapping("systemSearch")
|
@GetMapping("systemSearch")
|
||||||
public PageVO<OperationLogRecord> systemSearch(@RequestParam(required = false) String keyword,
|
public PageVO<OperationLogRecord> systemSearch(@RequestParam(required = false) String keyword,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import com.yunhai.bschclassmanage.utils.UserContext;
|
|||||||
|
|
||||||
|
|
||||||
import com.yunhai.bschclassmanage.config.Config;
|
import com.yunhai.bschclassmanage.config.Config;
|
||||||
import com.yunhai.platform.openapi.client.YschBaseOpenApi;
|
import com.yunhai.platform.openapi.base.YschBaseOpenApi;
|
||||||
import com.yunhai.platform.utils.model.BoaGroup;
|
import com.yunhai.platform.utils.model.BoaGroup;
|
||||||
import com.yunhai.platform.utils.model.BoaModule;
|
import com.yunhai.platform.utils.model.BoaModule;
|
||||||
import com.yunhai.platform.utils.model.BoaUser;
|
import com.yunhai.platform.utils.model.BoaUser;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import com.yunhai.bschclassmanage.model.dto.SendUserGroupDTO;
|
|||||||
import com.yunhai.bschclassmanage.model.vo.SelectIntVO;
|
import com.yunhai.bschclassmanage.model.vo.SelectIntVO;
|
||||||
import com.yunhai.bschclassmanage.service.ISendUserBaseService;
|
import com.yunhai.bschclassmanage.service.ISendUserBaseService;
|
||||||
import com.yunhai.bschclassmanage.utils.UserContext;
|
import com.yunhai.bschclassmanage.utils.UserContext;
|
||||||
import com.yunhai.platform.openapi.client.YschBaseOpenApi;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package com.yunhai.bschclassmanage.controller.api;
|
package com.yunhai.bschclassmanage.controller.api;
|
||||||
import com.yunhai.bschclassmanage.utils.UserContext;
|
import com.yunhai.bschclassmanage.utils.UserContext;
|
||||||
|
|
||||||
|
|
||||||
import com.yunhai.bschclassmanage.config.Config;
|
import com.yunhai.bschclassmanage.config.Config;
|
||||||
import com.yunhai.platform.openapi.client.YschBaseOpenApi;
|
import com.yunhai.platform.openapi.base.YschBaseOpenApi;
|
||||||
import com.yunhai.platform.utils.model.BoaGroup;
|
import com.yunhai.platform.utils.model.BoaGroup;
|
||||||
import com.yunhai.platform.utils.model.BoaModule;
|
import com.yunhai.platform.utils.model.BoaModule;
|
||||||
import com.yunhai.platform.utils.model.BoaUser;
|
import com.yunhai.platform.utils.model.BoaUser;
|
||||||
|
|||||||
@@ -20,14 +20,11 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.*;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/** Compatibility endpoints used by BestSchool.IBaseClass.BLL.DbClass consumers. */
|
/** Compatibility endpoints used by BestSchool.IBaseClass.BLL.DbClass consumers. */
|
||||||
|
|
||||||
@Tag(name = "DbClass", description = "School class data APIs")
|
@Tag(name = "DbClass", description = "School class data APIs")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(Config.OAPI + "/DbClass/")
|
@RequestMapping(Config.OAPI + "/DbClass/")
|
||||||
@@ -43,7 +40,7 @@ public class DbClassController {
|
|||||||
public ResponseEntity<List<Map<String, Object>>> getSchteaList(
|
public ResponseEntity<List<Map<String, Object>>> getSchteaList(
|
||||||
@Parameter(description = "School ID", required = true, example = "3")
|
@Parameter(description = "School ID", required = true, example = "3")
|
||||||
@RequestParam("Group") Integer group) {
|
@RequestParam("Group") Integer group) {
|
||||||
if (group == null || group <= 0) return ResponseEntity.badRequest().build();
|
if (group == null || group <= 0) return ResponseEntity.ok(new ArrayList<>());
|
||||||
|
|
||||||
SchInfoVO school = schInfoService.GetSchFirst(group);
|
SchInfoVO school = schInfoService.GetSchFirst(group);
|
||||||
String schSerId = school == null
|
String schSerId = school == null
|
||||||
@@ -69,6 +66,7 @@ public class DbClassController {
|
|||||||
row.put("SchSerID", schSerId);
|
row.put("SchSerID", schSerId);
|
||||||
row.put("TeaTel", teacher.getTel());
|
row.put("TeaTel", teacher.getTel());
|
||||||
row.put("TeaName", teacher.getName());
|
row.put("TeaName", teacher.getName());
|
||||||
|
row.put("TeaGender", teacher.getGender());
|
||||||
row.put("UserTeaSerID", teacher.getTeaId());
|
row.put("UserTeaSerID", teacher.getTeaId());
|
||||||
row.put("TeaIDCard", teacher.getIdCard());
|
row.put("TeaIDCard", teacher.getIdCard());
|
||||||
row.put("TeaNo", teacher.getSort());
|
row.put("TeaNo", teacher.getSort());
|
||||||
|
|||||||
@@ -4,12 +4,15 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import com.yunhai.bschclassmanage.config.Config;
|
import com.yunhai.bschclassmanage.config.Config;
|
||||||
|
import com.yunhai.bschclassmanage.model.oa.TeaTreeVO;
|
||||||
import com.yunhai.bschclassmanage.model.vo.oa.SchInfoVO;
|
import com.yunhai.bschclassmanage.model.vo.oa.SchInfoVO;
|
||||||
|
import com.yunhai.bschclassmanage.service.IComSchTreeService;
|
||||||
import com.yunhai.bschclassmanage.service.ISchInfoService;
|
import com.yunhai.bschclassmanage.service.ISchInfoService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Tag(name = "SchGroup", description = "SchGroup related APIs")
|
@Tag(name = "SchGroup", description = "SchGroup related APIs")
|
||||||
@@ -18,7 +21,51 @@ import java.util.List;
|
|||||||
@CrossOrigin(originPatterns = {"*"}, allowCredentials = "true")
|
@CrossOrigin(originPatterns = {"*"}, allowCredentials = "true")
|
||||||
public class SchGroupController {
|
public class SchGroupController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IComSchTreeService comSchTreeService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ISchInfoService schInfoService;
|
||||||
|
|
||||||
|
@GetMapping("GetTeaTree")
|
||||||
|
@Operation(summary = "获取学校教师组树(toUser 默认 true 带教师,false 仅返回组骨架;SchSerID 与 schId 二选一)")
|
||||||
|
public ResponseEntity<List<TeaTreeVO>> getTeaTree(@RequestParam(value = "SchSerID", required = false) String SchSerID,
|
||||||
|
@RequestParam(value = "schId", required = false) Integer schId,
|
||||||
|
@RequestParam(value = "toUser", required = false, defaultValue = "true") boolean toUser) {
|
||||||
|
Integer resolvedSchId = resolveSchId(schId, SchSerID);
|
||||||
|
if (resolvedSchId == null) {
|
||||||
|
return ResponseEntity.ok(Collections.emptyList());
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(comSchTreeService.getSchTeaTree(resolvedSchId, toUser));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("GetStuTree")
|
||||||
|
@Operation(summary = "获取学校学生树:学校→学段→年级→班级→学生(toUser 默认 true 带学生,false 仅返回组织骨架;SchSerID 与 schId 二选一)")
|
||||||
|
public ResponseEntity<List<TeaTreeVO>> getStuTree(@RequestParam(value = "SchSerID", required = false) String SchSerID,
|
||||||
|
@RequestParam(value = "schId", required = false) Integer schId,
|
||||||
|
@RequestParam(value = "toUser", required = false, defaultValue = "true") boolean toUser) {
|
||||||
|
Integer resolvedSchId = resolveSchId(schId, SchSerID);
|
||||||
|
if (resolvedSchId == null) {
|
||||||
|
return ResponseEntity.ok(Collections.emptyList());
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(comSchTreeService.getSchStuTree(resolvedSchId, toUser));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析学校ID:支持直接传 schId,或传 SchSerID 反查 schId,二者至少提供一个。
|
||||||
|
*
|
||||||
|
* @param schId 学校内部ID(优先使用)
|
||||||
|
* @param schSerID 学校外部序列号
|
||||||
|
* @return 学校内部ID,参数非法或查不到时返回 null
|
||||||
|
*/
|
||||||
|
private Integer resolveSchId(Integer schId, String schSerID) {
|
||||||
|
if (schId != null) {
|
||||||
|
return schId;
|
||||||
|
}
|
||||||
|
if (schSerID == null || schSerID.trim().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
SchInfoVO schInfoVO = schInfoService.GetSchFirst(schSerID);
|
||||||
|
return schInfoVO == null ? null : schInfoVO.getUserSchSerId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public class SchSubController {
|
|||||||
if (SchID == null || SchID.trim().isEmpty()) return ResponseEntity.ok(Collections.emptyList());
|
if (SchID == null || SchID.trim().isEmpty()) return ResponseEntity.ok(Collections.emptyList());
|
||||||
Optional<SchInfo> school = schInfoRepository.findBySchSerId(SchID);
|
Optional<SchInfo> school = schInfoRepository.findBySchSerId(SchID);
|
||||||
if (!school.isPresent()) return ResponseEntity.ok(Collections.emptyList());
|
if (!school.isPresent()) return ResponseEntity.ok(Collections.emptyList());
|
||||||
return ResponseEntity.ok(subjectService.getSchSubList(school.get().getSchId(), schooling).stream().map(s -> subject(s, school.get().getSchSerId())).collect(Collectors.toList()));
|
return ResponseEntity.ok(subjectService.getSchSubList(school.get().getSchId(), schooling).stream().filter(this::enabled).map(s -> subject(s, school.get().getSchSerId())).collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("GetSchIdsSublist")
|
@GetMapping("GetSchIdsSublist")
|
||||||
@@ -37,7 +37,7 @@ public class SchSubController {
|
|||||||
if (SchIds == null || SchIds.trim().isEmpty()) return ResponseEntity.ok(Collections.emptyList());
|
if (SchIds == null || SchIds.trim().isEmpty()) return ResponseEntity.ok(Collections.emptyList());
|
||||||
List<String> ids = OaCompatSupport.strings(SchIds);
|
List<String> ids = OaCompatSupport.strings(SchIds);
|
||||||
List<Map<String,Object>> out = new ArrayList<>();
|
List<Map<String,Object>> out = new ArrayList<>();
|
||||||
for (String id : ids) schInfoRepository.findBySchSerId(id).ifPresent(s -> subjectService.getSchSubList(s.getSchId(), null).forEach(sub -> out.add(subject(sub, s.getSchSerId()))));
|
for (String id : ids) schInfoRepository.findBySchSerId(id).ifPresent(s -> subjectService.getSchSubList(s.getSchId(), null).stream().filter(this::enabled).forEach(sub -> out.add(subject(sub, s.getSchSerId()))));
|
||||||
return ResponseEntity.ok(out);
|
return ResponseEntity.ok(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +47,10 @@ public class SchSubController {
|
|||||||
if (UserTeaSerID == null) return ResponseEntity.ok(Collections.emptyList());
|
if (UserTeaSerID == null) return ResponseEntity.ok(Collections.emptyList());
|
||||||
try {
|
try {
|
||||||
Integer teaId = Integer.valueOf(UserTeaSerID);
|
Integer teaId = Integer.valueOf(UserTeaSerID);
|
||||||
return ResponseEntity.ok(teachRepository.findAll().stream().filter(t -> Objects.equals(t.getTeaId(), teaId)).map(SchClassTeach::getSubId).filter(Objects::nonNull).distinct().collect(Collectors.toList()));
|
List<SchClassTeach> teaches = teachRepository.findAll().stream().filter(t -> Objects.equals(t.getTeaId(), teaId)).collect(Collectors.toList());
|
||||||
|
Set<String> enabledCodes = new HashSet<>();
|
||||||
|
teaches.stream().map(SchClassTeach::getSchId).filter(Objects::nonNull).distinct().forEach(schId -> enabledCodes.addAll(enabledSubjectCodes(schId)));
|
||||||
|
return ResponseEntity.ok(teaches.stream().map(SchClassTeach::getSubId).filter(c -> c != null && enabledCodes.contains(c)).distinct().collect(Collectors.toList()));
|
||||||
} catch (NumberFormatException e) { return ResponseEntity.ok(Collections.emptyList()); }
|
} catch (NumberFormatException e) { return ResponseEntity.ok(Collections.emptyList()); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,9 +60,14 @@ public class SchSubController {
|
|||||||
if (SchID == null) return ResponseEntity.ok(Collections.emptyList());
|
if (SchID == null) return ResponseEntity.ok(Collections.emptyList());
|
||||||
Optional<SchInfo> school = schInfoRepository.findBySchSerId(SchID);
|
Optional<SchInfo> school = schInfoRepository.findBySchSerId(SchID);
|
||||||
if (!school.isPresent()) return ResponseEntity.ok(Collections.emptyList());
|
if (!school.isPresent()) return ResponseEntity.ok(Collections.emptyList());
|
||||||
return ResponseEntity.ok(teachRepository.findAll().stream().filter(t -> Objects.equals(t.getSchId(), school.get().getSchId())).map(SchClassTeach::getSubId).filter(Objects::nonNull).map(s -> s.toLowerCase(Locale.ROOT)).distinct().collect(Collectors.toList()));
|
Set<String> enabledCodes = enabledSubjectCodes(school.get().getSchId()).stream().map(c -> c.toLowerCase(Locale.ROOT)).collect(Collectors.toSet());
|
||||||
|
return ResponseEntity.ok(teachRepository.findAll().stream().filter(t -> Objects.equals(t.getSchId(), school.get().getSchId())).map(SchClassTeach::getSubId).filter(Objects::nonNull).map(s -> s.toLowerCase(Locale.ROOT)).filter(enabledCodes::contains).distinct().collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean enabled(SchDicSubject s) { return Boolean.TRUE.equals(s.getEnable()); }
|
||||||
|
|
||||||
|
private Set<String> enabledSubjectCodes(Integer schId) { return subjectService.toEnableIdAndNameMap(schId, null).keySet(); }
|
||||||
|
|
||||||
private Map<String,Object> subject(SchDicSubject s, String schSerId) {
|
private Map<String,Object> subject(SchDicSubject s, String schSerId) {
|
||||||
Map<String,Object> m = new LinkedHashMap<>(); m.put("SchSerID", schSerId); m.put("SubID", s.getCode() == null ? String.valueOf(s.getId()) : s.getCode()); m.put("SubName", s.getName()); m.put("No", s.getSort()); return m;
|
Map<String,Object> m = new LinkedHashMap<>(); m.put("SchSerID", schSerId); m.put("SubID", s.getCode() == null ? String.valueOf(s.getId()) : s.getCode()); m.put("SubName", s.getName()); m.put("No", s.getSort()); return m;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ public class StuInfoController {
|
|||||||
@Autowired private SchStudentRepository studentRepository;
|
@Autowired private SchStudentRepository studentRepository;
|
||||||
@Autowired private SchInfoRepository schoolRepository;
|
@Autowired private SchInfoRepository schoolRepository;
|
||||||
@Autowired private SchStudentHistoryRepository historyRepository;
|
@Autowired private SchStudentHistoryRepository historyRepository;
|
||||||
|
@Autowired private SchClassRepository classRepository;
|
||||||
|
@Autowired private BaseSchsystemGradeRepository baseSchsystemGradeRepository;
|
||||||
|
|
||||||
@GetMapping("GetAllStulist")
|
@GetMapping("GetAllStulist")
|
||||||
@Operation(summary = "GetAllStulist")
|
@Operation(summary = "GetAllStulist")
|
||||||
@@ -58,6 +60,39 @@ public class StuInfoController {
|
|||||||
Set<Integer> ids = new HashSet<>(OaCompatSupport.ints(stuids));
|
Set<Integer> ids = new HashSet<>(OaCompatSupport.ints(stuids));
|
||||||
return ResponseEntity.ok(students(studentRepository.findAll().stream().filter(s -> ids.contains(s.getStuId())).collect(Collectors.toList()), null));
|
return ResponseEntity.ok(students(studentRepository.findAll().stream().filter(s -> ids.contains(s.getStuId())).collect(Collectors.toList()), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 按学校+年级直查学生(CampusNotice 推送范围圈人:免逐班聚合) */
|
||||||
|
@GetMapping("GetGradeStulist")
|
||||||
|
@Operation(summary = "GetGradeStulist", description = "按学校+年级查询在校学生(不含已毕业班级)")
|
||||||
|
public ResponseEntity<List<Map<String,Object>>> GetGradeStulist(@RequestParam(required=false) String SchSerID, @RequestParam(required=false) Integer GradeId) {
|
||||||
|
Integer schId = SchSerID == null ? null : schoolRepository.findBySchSerId(SchSerID).map(SchInfo::getSchId).orElse(null);
|
||||||
|
if (schId == null) return ResponseEntity.ok(Collections.emptyList());
|
||||||
|
Set<Integer> classIds = activeClassIds(schId, null, GradeId == null ? null : Collections.singleton(GradeId));
|
||||||
|
if (classIds.isEmpty()) return ResponseEntity.ok(Collections.emptyList());
|
||||||
|
return ResponseEntity.ok(students(studentRepository.findAll().stream().filter(s -> classIds.contains(s.getClassId())).collect(Collectors.toList()), SchSerID));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 按学校+学段(学制)直查学生(CampusNotice「学部」级范围一键展开) */
|
||||||
|
@GetMapping("GetSubStulist")
|
||||||
|
@Operation(summary = "GetSubStulist", description = "按学校+学段查询在校学生(不含已毕业班级)")
|
||||||
|
public ResponseEntity<List<Map<String,Object>>> GetSubStulist(@RequestParam(required=false) String SchSerID, @RequestParam(required=false) Integer SchSystemId) {
|
||||||
|
Integer schId = SchSerID == null ? null : schoolRepository.findBySchSerId(SchSerID).map(SchInfo::getSchId).orElse(null);
|
||||||
|
if (schId == null || SchSystemId == null) return ResponseEntity.ok(Collections.emptyList());
|
||||||
|
Set<Integer> gradeIds = baseSchsystemGradeRepository.findBySchSystemId(SchSystemId).stream()
|
||||||
|
.map(BaseSchsystemGrade::getGradeId).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||||
|
Set<Integer> classIds = activeClassIds(schId, gradeIds, null);
|
||||||
|
if (classIds.isEmpty()) return ResponseEntity.ok(Collections.emptyList());
|
||||||
|
return ResponseEntity.ok(students(studentRepository.findAll().stream().filter(s -> classIds.contains(s.getClassId())).collect(Collectors.toList()), SchSerID));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 学校在读班级 class_id 集合:gradeIds 非空按年级集合过滤,单个 gradeId 传 singleton */
|
||||||
|
private Set<Integer> activeClassIds(Integer schId, Set<Integer> gradeIds, Set<Integer> singleGrade) {
|
||||||
|
return classRepository.findAll().stream()
|
||||||
|
.filter(c -> Objects.equals(c.getSchId(), schId) && !Boolean.TRUE.equals(c.getGraduate()))
|
||||||
|
.filter(c -> (gradeIds == null || gradeIds.contains(c.getGradeId())) && (singleGrade == null || singleGrade.contains(c.getGradeId())))
|
||||||
|
.map(SchClass::getClassId).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("GetEleStuIdslist")
|
@GetMapping("GetEleStuIdslist")
|
||||||
@Operation(summary = "GetEleStuIdslist")
|
@Operation(summary = "GetEleStuIdslist")
|
||||||
public ResponseEntity<List<Map<String,Object>>> GetEleStuIdslist(@RequestParam(required=false) String stuids) { return GetStuIdslist(stuids); }
|
public ResponseEntity<List<Map<String,Object>>> GetEleStuIdslist(@RequestParam(required=false) String stuids) { return GetStuIdslist(stuids); }
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
import com.yunhai.bschclassmanage.Repository.*;
|
import com.yunhai.bschclassmanage.Repository.*;
|
||||||
import com.yunhai.bschclassmanage.config.Config;
|
import com.yunhai.bschclassmanage.config.Config;
|
||||||
import com.yunhai.bschclassmanage.entity.*;
|
import com.yunhai.bschclassmanage.entity.*;
|
||||||
|
import com.yunhai.bschclassmanage.enums.TransferStatus;
|
||||||
|
import com.yunhai.bschclassmanage.service.*;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
@@ -19,12 +21,16 @@ import java.util.stream.Collectors;
|
|||||||
public class TeaChesController {
|
public class TeaChesController {
|
||||||
@Autowired private SchClassTeachRepository teachingRepository;
|
@Autowired private SchClassTeachRepository teachingRepository;
|
||||||
@Autowired private SchInfoRepository schoolRepository;
|
@Autowired private SchInfoRepository schoolRepository;
|
||||||
|
@Autowired private SchTeacherGroupRepository teacherGroupRepository;
|
||||||
|
@Autowired private ISchClassService classService;
|
||||||
|
@Autowired private ISchDicSubjectService subjectService;
|
||||||
|
|
||||||
@GetMapping("GetSchTeachlist")
|
@GetMapping("GetSchTeachlist")
|
||||||
@Operation(summary = "GetSchTeach")
|
@Operation(summary = "GetSchTeach")
|
||||||
public ResponseEntity<List<Map<String,Object>>> GetSchTeach(@RequestParam(required=false) String SchID, @RequestParam(required=false) Integer UserTeaSerID) {
|
public ResponseEntity<List<Map<String,Object>>> GetSchTeach(@RequestParam(required=false) String SchID, @RequestParam(required=false) Integer UserTeaSerID) {
|
||||||
Integer schId = SchID == null ? null : schoolRepository.findBySchSerId(SchID).map(SchInfo::getSchId).orElse(null);
|
Integer schId = SchID == null ? null : schoolRepository.findBySchSerId(SchID).map(SchInfo::getSchId).orElse(null);
|
||||||
return ResponseEntity.ok(teachingRepository.findAll().stream().filter(t -> (schId == null || Objects.equals(t.getSchId(), schId)) && (UserTeaSerID == null || Objects.equals(t.getTeaId(), UserTeaSerID))).map(t -> OaCompatSupport.teaching(t, schoolSer(t.getSchId()))).collect(Collectors.toList()));
|
List<SchClassTeach> list = teachingRepository.findAll().stream().filter(t -> (schId == null || Objects.equals(t.getSchId(), schId)) && (UserTeaSerID == null || Objects.equals(t.getTeaId(), UserTeaSerID))).collect(Collectors.toList());
|
||||||
|
return ResponseEntity.ok(normalFiltered(list).stream().map(t -> OaCompatSupport.teaching(t, schoolSer(t.getSchId()))).collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
@GetMapping("GetEleSchTeachlist")
|
@GetMapping("GetEleSchTeachlist")
|
||||||
@Operation(summary = "GetEleSchTeachlist")
|
@Operation(summary = "GetEleSchTeachlist")
|
||||||
@@ -39,7 +45,8 @@ public class TeaChesController {
|
|||||||
@Operation(summary = "GetClassTeach")
|
@Operation(summary = "GetClassTeach")
|
||||||
public ResponseEntity<List<Map<String,Object>>> GetClassTeach(@RequestParam(required=false) String ClassID) {
|
public ResponseEntity<List<Map<String,Object>>> GetClassTeach(@RequestParam(required=false) String ClassID) {
|
||||||
Integer classId = intValue(ClassID);
|
Integer classId = intValue(ClassID);
|
||||||
return ResponseEntity.ok(teachingRepository.findAll().stream().filter(t -> classId != null && Objects.equals(t.getClassId(), classId)).map(t -> OaCompatSupport.teaching(t, schoolSer(t.getSchId()))).collect(Collectors.toList()));
|
List<SchClassTeach> list = teachingRepository.findAll().stream().filter(t -> classId != null && Objects.equals(t.getClassId(), classId)).collect(Collectors.toList());
|
||||||
|
return ResponseEntity.ok(normalFiltered(list).stream().map(t -> OaCompatSupport.teaching(t, schoolSer(t.getSchId()))).collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
@GetMapping("GetEleSchClaTeachlist")
|
@GetMapping("GetEleSchClaTeachlist")
|
||||||
@Operation(summary = "GetEleSchClaTeachlist")
|
@Operation(summary = "GetEleSchClaTeachlist")
|
||||||
@@ -53,7 +60,22 @@ public class TeaChesController {
|
|||||||
|
|
||||||
private ResponseEntity<List<Map<String,Object>>> bySchools(String value) {
|
private ResponseEntity<List<Map<String,Object>>> bySchools(String value) {
|
||||||
Set<Integer> ids = new HashSet<>(); for (String ser : OaCompatSupport.strings(value)) schoolRepository.findBySchSerId(ser).ifPresent(s -> ids.add(s.getSchId()));
|
Set<Integer> ids = new HashSet<>(); for (String ser : OaCompatSupport.strings(value)) schoolRepository.findBySchSerId(ser).ifPresent(s -> ids.add(s.getSchId()));
|
||||||
return ResponseEntity.ok(teachingRepository.findAll().stream().filter(t -> ids.contains(t.getSchId())).map(t -> OaCompatSupport.teaching(t, schoolSer(t.getSchId()))).collect(Collectors.toList()));
|
List<SchClassTeach> list = teachingRepository.findAll().stream().filter(t -> ids.contains(t.getSchId())).collect(Collectors.toList());
|
||||||
|
return ResponseEntity.ok(normalFiltered(list).stream().map(t -> OaCompatSupport.teaching(t, schoolSer(t.getSchId()))).collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
/** 只保留正常教师(sch_teacher_group 中 transfer_status=转入)、正常班级(未毕业)、启用学科(enable=true)的任教记录,按学校分别过滤 */
|
||||||
|
private List<SchClassTeach> normalFiltered(List<SchClassTeach> teaches) {
|
||||||
|
List<SchClassTeach> out = new ArrayList<>();
|
||||||
|
Map<Integer, List<SchClassTeach>> bySch = new HashMap<>();
|
||||||
|
for (SchClassTeach t : teaches) if (t.getSchId() != null) bySch.computeIfAbsent(t.getSchId(), k -> new ArrayList<>()).add(t);
|
||||||
|
for (Map.Entry<Integer, List<SchClassTeach>> e : bySch.entrySet()) {
|
||||||
|
Integer schId = e.getKey();
|
||||||
|
Set<Integer> teaIds = teacherGroupRepository.findBySchIdAndTransferStatus(schId, TransferStatus.IN.getId()).stream().map(SchTeacherGroup::getTeaId).collect(Collectors.toSet());
|
||||||
|
Set<Integer> classIds = classService.getSchAllClassCacheList(schId).stream().filter(c -> !Boolean.TRUE.equals(c.getGraduate()) && c.getClassId() != null).map(SchClass::getClassId).collect(Collectors.toSet());
|
||||||
|
Set<String> subCodes = subjectService.toEnableIdAndNameMap(schId, null).keySet();
|
||||||
|
for (SchClassTeach t : e.getValue()) if (teaIds.contains(t.getTeaId()) && classIds.contains(t.getClassId()) && t.getSubId() != null && subCodes.contains(t.getSubId())) out.add(t);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
private Integer intValue(String s) { try { return s == null ? null : Integer.valueOf(s); } catch (Exception e) { return null; } }
|
private Integer intValue(String s) { try { return s == null ? null : Integer.valueOf(s); } catch (Exception e) { return null; } }
|
||||||
private String schoolSer(Integer id) { return id == null ? null : schoolRepository.findBySchId(id).map(SchInfo::getSchSerId).orElse(null); }
|
private String schoolSer(Integer id) { return id == null ? null : schoolRepository.findBySchId(id).map(SchInfo::getSchSerId).orElse(null); }
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.yunhai.bschclassmanage.controller.oapi.oa;
|
|||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import com.yunhai.bschclassmanage.Repository.SchTeacherGroupRepository;
|
||||||
import com.yunhai.bschclassmanage.Repository.SchTeacherRepository;
|
import com.yunhai.bschclassmanage.Repository.SchTeacherRepository;
|
||||||
import com.yunhai.bschclassmanage.config.Config;
|
import com.yunhai.bschclassmanage.config.Config;
|
||||||
import com.yunhai.bschclassmanage.entity.SchTeacher;
|
import com.yunhai.bschclassmanage.entity.SchTeacher;
|
||||||
@@ -21,7 +22,10 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -37,6 +41,9 @@ public class TeainfoController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private SchTeacherRepository schTeacherRepository;
|
private SchTeacherRepository schTeacherRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SchTeacherGroupRepository schTeacherGroupRepository;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ISchInfoService schInfoService;
|
private ISchInfoService schInfoService;
|
||||||
|
|
||||||
@@ -45,14 +52,19 @@ public class TeainfoController {
|
|||||||
public ResponseEntity<TeaInfoVO> getTeainfo(@RequestParam Integer uid) {
|
public ResponseEntity<TeaInfoVO> getTeainfo(@RequestParam Integer uid) {
|
||||||
// 1. 鍙傛暟鏍¢獙
|
// 1. 鍙傛暟鏍¢獙
|
||||||
if (uid == null) {
|
if (uid == null) {
|
||||||
return ResponseEntity.badRequest().build();
|
return ResponseEntity.ok(new TeaInfoVO());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 鐩存帴浣跨敤 uid 鏌ヨ
|
// 2. 鐩存帴浣跨敤 uid 鏌ヨ
|
||||||
return schTeacherRepository.findByTeaId(uid) // 鍋囪 findByTeaId 杩斿洖 Optional<SchTeacher>
|
return schTeacherRepository.findByTeaId(uid).stream()
|
||||||
.map(this::convertToTeaInfoVO) // 灏?SchTeacher 杞负 TeaInfoVO
|
.findFirst()
|
||||||
|
.map(teacher -> {
|
||||||
|
fillGroupList(Collections.singletonList(teacher), null);
|
||||||
|
return teacher;
|
||||||
|
})
|
||||||
|
.map(this::convertToTeaInfoVO)
|
||||||
.map(ResponseEntity::ok)
|
.map(ResponseEntity::ok)
|
||||||
.orElse(ResponseEntity.notFound().build());
|
.orElse(ResponseEntity.ok(new TeaInfoVO()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private TeaInfoVO convertToTeaInfoVO(SchTeacher teacher) {
|
private TeaInfoVO convertToTeaInfoVO(SchTeacher teacher) {
|
||||||
@@ -94,22 +106,48 @@ public class TeainfoController {
|
|||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量回填教师的分组列表(groupList 为 @Transient 字段,查询后需要单独填充)。
|
||||||
|
* schId 不为空时只查该校的组关系,保证取到的 GroupID/SchSerID 属于当前学校。
|
||||||
|
*/
|
||||||
|
private void fillGroupList(List<SchTeacher> teachers, Integer schId) {
|
||||||
|
if (teachers == null || teachers.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<Integer> teaIds = teachers.stream()
|
||||||
|
.map(SchTeacher::getTeaId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (teaIds.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<SchTeacherGroup> relations = (schId != null)
|
||||||
|
? schTeacherGroupRepository.findByTeaIdInAndSchId(teaIds, schId)
|
||||||
|
: schTeacherGroupRepository.findByTeaIdIn(teaIds);
|
||||||
|
Map<Integer, List<SchTeacherGroup>> groupMap = relations.stream()
|
||||||
|
.filter(g -> g.getTeaId() != null)
|
||||||
|
.collect(Collectors.groupingBy(SchTeacherGroup::getTeaId));
|
||||||
|
teachers.forEach(t -> t.setGroupList(groupMap.getOrDefault(t.getTeaId(), Collections.emptyList())));
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("GetArrTeainfo")
|
@PostMapping("GetArrTeainfo")
|
||||||
@Operation(summary = "根据教师id数组 获取教师列表")
|
@Operation(summary = "根据教师id数组 获取教师列表")
|
||||||
public ResponseEntity<List<TeaInfoVO>> getArrTeainfo(@RequestBody String userIds) {
|
public ResponseEntity<List<TeaInfoVO>> getArrTeainfo(@RequestBody String userIds) {
|
||||||
// 1. 鍙傛暟鏍¢獙
|
// 1. 鍙傛暟鏍¢獙
|
||||||
if (userIds == null) {
|
if (userIds == null) {
|
||||||
return ResponseEntity.badRequest().build();
|
return ResponseEntity.ok(new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Integer> uids= Arrays.asList(OtherUtil.convertToArray(userIds));
|
List<Integer> uids= Arrays.asList(OtherUtil.convertToArray(userIds));
|
||||||
|
|
||||||
List<SchTeacher> teachers = schTeacherRepository.findByTeaIdIn(uids);
|
List<SchTeacher> teachers = schTeacherRepository.findByTeaIdIn(uids);
|
||||||
|
fillGroupList(teachers, null);
|
||||||
List<TeaInfoVO> voList = teachers.stream()
|
List<TeaInfoVO> voList = teachers.stream()
|
||||||
.map(this::convertToTeaInfoVO)
|
.map(this::convertToTeaInfoVO)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
return voList.isEmpty() ? ResponseEntity.noContent().build() : ResponseEntity.ok(voList);
|
return ResponseEntity.ok(voList);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -137,16 +175,18 @@ public class TeainfoController {
|
|||||||
// 1. 参数校验:SchSerID 与 schId 至少传一个
|
// 1. 参数校验:SchSerID 与 schId 至少传一个
|
||||||
Integer resolvedSchId = resolveSchId(schId, SchSerID);
|
Integer resolvedSchId = resolveSchId(schId, SchSerID);
|
||||||
if (resolvedSchId == null) {
|
if (resolvedSchId == null) {
|
||||||
return ResponseEntity.badRequest().build();
|
return ResponseEntity.ok(new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
List<SchTeacher> teachers = schTeacherService.getSchTeacherList(resolvedSchId);
|
List<SchTeacher> teachers = schTeacherService.getSchTeacherList(resolvedSchId);
|
||||||
|
fillGroupList(teachers, resolvedSchId);
|
||||||
|
|
||||||
List<TeaInfoVO> voList = teachers.stream()
|
List<TeaInfoVO> voList = teachers.stream()
|
||||||
.map(this::convertToTeaInfoVO)
|
.map(this::convertToTeaInfoVO)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
return voList.isEmpty() ? ResponseEntity.noContent().build() : ResponseEntity.ok(voList);
|
return ResponseEntity.ok(voList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("GetSchTeaList")
|
@GetMapping("GetSchTeaList")
|
||||||
|
|||||||
@@ -52,12 +52,12 @@ public class UserInfoController {
|
|||||||
public ResponseEntity<List<UserInfoVO>> getUserList(@RequestParam String SchSerID) {
|
public ResponseEntity<List<UserInfoVO>> getUserList(@RequestParam String SchSerID) {
|
||||||
// 1. 鍙傛暟鏍¢獙
|
// 1. 鍙傛暟鏍¢獙
|
||||||
if (SchSerID == null || SchSerID.trim().isEmpty()) {
|
if (SchSerID == null || SchSerID.trim().isEmpty()) {
|
||||||
return ResponseEntity.badRequest().build();
|
return ResponseEntity.ok(new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
SchInfoVO schInfoVO = schInfoService.GetSchFirst(SchSerID);
|
SchInfoVO schInfoVO = schInfoService.GetSchFirst(SchSerID);
|
||||||
if (schInfoVO == null) {
|
if (schInfoVO == null) {
|
||||||
return ResponseEntity.badRequest().build();
|
return ResponseEntity.ok(new ArrayList<>());
|
||||||
}
|
}
|
||||||
Integer schId = schInfoVO.getUserSchSerId();
|
Integer schId = schInfoVO.getUserSchSerId();
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ public class UserInfoController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 4. 杩斿洖缁撴灉
|
// 4. 杩斿洖缁撴灉
|
||||||
return voList.isEmpty() ? ResponseEntity.noContent().build() : ResponseEntity.ok(voList);
|
return ResponseEntity.ok(voList);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== 杞崲鏁欏笀 =====
|
// ===== 杞崲鏁欏笀 =====
|
||||||
@@ -157,10 +157,11 @@ public class UserInfoController {
|
|||||||
@Operation(summary = "getUserIdList")
|
@Operation(summary = "getUserIdList")
|
||||||
public ResponseEntity<List<UserInfoVO>> getUserIdList(@RequestParam Integer userid) {
|
public ResponseEntity<List<UserInfoVO>> getUserIdList(@RequestParam Integer userid) {
|
||||||
if (userid == null) {
|
if (userid == null) {
|
||||||
return ResponseEntity.badRequest().build();
|
return ResponseEntity.ok(new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<SchTeacher> teacherOpt = schTeacherRepository.findByTeaId(userid);
|
// 同一 teaId/stuId 可能在多所学校各有记录,取第一条即可(不能按唯一查询)
|
||||||
|
Optional<SchTeacher> teacherOpt = schTeacherRepository.findByTeaId(userid).stream().findFirst();
|
||||||
if (teacherOpt.isPresent()) {
|
if (teacherOpt.isPresent()) {
|
||||||
UserInfoVO vo = convertToUserInfoVO(teacherOpt.get());
|
UserInfoVO vo = convertToUserInfoVO(teacherOpt.get());
|
||||||
if (vo != null) {
|
if (vo != null) {
|
||||||
@@ -168,15 +169,14 @@ public class UserInfoController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<SchStudent> studentOpt = schStudentRepository.findByStuId(userid);
|
Optional<SchStudent> studentOpt = schStudentRepository.findByStuId(userid).stream().findFirst();
|
||||||
if (studentOpt.isPresent()) {
|
if (studentOpt.isPresent()) {
|
||||||
UserInfoVO vo = convertToUserInfoVO(studentOpt.get());
|
UserInfoVO vo = convertToUserInfoVO(studentOpt.get());
|
||||||
if (vo != null) {
|
if (vo != null) {
|
||||||
return ResponseEntity.ok(Collections.singletonList(vo));
|
return ResponseEntity.ok(Collections.singletonList(vo));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return ResponseEntity.ok(new ArrayList<>());
|
||||||
return ResponseEntity.notFound().build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+182
@@ -0,0 +1,182 @@
|
|||||||
|
package com.yunhai.bschclassmanage.controller.oapi.sch;
|
||||||
|
|
||||||
|
import com.yunhai.bschclassmanage.Repository.SchClassRepository;
|
||||||
|
import com.yunhai.bschclassmanage.Repository.SchInfoRepository;
|
||||||
|
import com.yunhai.bschclassmanage.config.Config;
|
||||||
|
import com.yunhai.bschclassmanage.entity.SchClass;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.sch.OapiClassVO;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.sch.OapiGradeNameVO;
|
||||||
|
import com.yunhai.bschclassmanage.service.IBaseSchsystemGradeService;
|
||||||
|
import com.yunhai.bschclassmanage.service.IBaseSchsystemService;
|
||||||
|
import com.yunhai.bschclassmanage.service.ISchBinSchsystemService;
|
||||||
|
import com.yunhai.bschclassmanage.service.ISchClassService;
|
||||||
|
import com.yunhai.bschclassmanage.service.ISchTeacherService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Tag(name = "OapiSchClass", description = "开放接口-班级(含班主任)")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(Config.OAPI + "/sch/")
|
||||||
|
@CrossOrigin(originPatterns = {"*"}, allowCredentials = "true")
|
||||||
|
public class OapiSchClassController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SchClassRepository schClassRepository;
|
||||||
|
@Autowired
|
||||||
|
private SchInfoRepository schInfoRepository;
|
||||||
|
@Autowired
|
||||||
|
private ISchClassService schClassService;
|
||||||
|
@Autowired
|
||||||
|
private ISchTeacherService schTeacherService;
|
||||||
|
@Autowired
|
||||||
|
private IBaseSchsystemGradeService baseSchsystemGradeService;
|
||||||
|
@Autowired
|
||||||
|
private ISchBinSchsystemService schBinSchsystemService;
|
||||||
|
@Autowired
|
||||||
|
private IBaseSchsystemService baseSchsystemService;
|
||||||
|
|
||||||
|
@GetMapping("getClassInfo")
|
||||||
|
@Operation(summary = "获取班级信息", description = "按班级ID(班级组ID)查询班级信息,含班主任/副班主任ID与姓名;可传 schId 精确到学校")
|
||||||
|
public ResponseEntity<OapiClassVO> getClassInfo(@RequestParam(value = "classId", required = false) Integer classId,
|
||||||
|
@RequestParam(value = "schId", required = false) Integer schId) {
|
||||||
|
if (classId == null) {
|
||||||
|
return ResponseEntity.ok(null);
|
||||||
|
}
|
||||||
|
Optional<SchClass> first = schId != null
|
||||||
|
? schClassRepository.findByClassIdAndSchId(classId, schId)
|
||||||
|
: schClassRepository.findAll().stream()
|
||||||
|
.filter(c -> classId.equals(c.getClassId()))
|
||||||
|
.findFirst();
|
||||||
|
return ResponseEntity.ok(first.map(c -> toVOList(Collections.singletonList(c)).get(0)).orElse(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("getGradeClassList")
|
||||||
|
@Operation(summary = "获取年级班级列表", description = "按年级编码查询班级列表;建议同时传 schId(走学校缓存),不传则跨校查询")
|
||||||
|
public ResponseEntity<List<OapiClassVO>> getGradeClassList(@RequestParam(value = "gradeId", required = false) Integer gradeId,
|
||||||
|
@RequestParam(value = "schId", required = false) Integer schId) {
|
||||||
|
if (gradeId == null) {
|
||||||
|
return ResponseEntity.ok(Collections.emptyList());
|
||||||
|
}
|
||||||
|
List<SchClass> classes = schId != null
|
||||||
|
? schClassService.getSchClassCacheFilter(schId, gradeId, null)
|
||||||
|
: schClassRepository.findAll().stream()
|
||||||
|
.filter(c -> gradeId.equals(c.getGradeId()) && !Boolean.TRUE.equals(c.getGraduate()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return ResponseEntity.ok(toVOList(classes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("getSchClassList")
|
||||||
|
@Operation(summary = "获取全校班级列表", description = "按学校ID查询全部班级(含班主任);默认不含毕业班,includeGraduate=true 时含毕业班")
|
||||||
|
public ResponseEntity<List<OapiClassVO>> getSchClassList(@RequestParam(value = "schId", required = false) Integer schId,
|
||||||
|
@RequestParam(value = "includeGraduate", required = false, defaultValue = "false") boolean includeGraduate) {
|
||||||
|
if (schId == null) {
|
||||||
|
return ResponseEntity.ok(Collections.emptyList());
|
||||||
|
}
|
||||||
|
List<SchClass> classes = includeGraduate
|
||||||
|
? schClassService.getSchAllClassCacheList(schId)
|
||||||
|
: schClassService.getSchClassCacheList(schId);
|
||||||
|
return ResponseEntity.ok(toVOList(classes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("getGradeName")
|
||||||
|
@Operation(summary = "获取年级名称", description = "按学校ID+入学年份+学段(1小学/2初中/3高中)查询学校年级字典名称(gradeNickname);"
|
||||||
|
+ "年份按当前学年与年级序换算(与班级导入同口径),无匹配学制/年级返回 null,由调用方自行降级")
|
||||||
|
public ResponseEntity<OapiGradeNameVO> getGradeName(@RequestParam("schId") Integer schId,
|
||||||
|
@RequestParam("year") Integer year,
|
||||||
|
@RequestParam(value = "stage", required = false) Integer stage) {
|
||||||
|
if (schId == null || year == null) {
|
||||||
|
return ResponseEntity.ok(null);
|
||||||
|
}
|
||||||
|
// 学段 → 学制:按学校各学制记录的学段名(小学/初中/高中)匹配;不传 stage 时不限学段
|
||||||
|
String stageName = stage == null ? null
|
||||||
|
: stage == 1 ? "小学" : stage == 2 ? "初中" : stage == 3 ? "高中" : null;
|
||||||
|
List<Integer> targetSystems = new ArrayList<>();
|
||||||
|
if (stageName == null) {
|
||||||
|
targetSystems.addAll(schBinSchsystemService.getSchBinSchsystemIds(schId));
|
||||||
|
} else {
|
||||||
|
for (Integer schsystemId : schBinSchsystemService.getSchBinSchsystemIds(schId)) {
|
||||||
|
try {
|
||||||
|
String name = baseSchsystemService.getAcademicStageName(schsystemId);
|
||||||
|
if (name != null && name.contains(stageName)) {
|
||||||
|
targetSystems.add(schsystemId);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 单个学制学段名缺失(学制未找到等)跳过,不影响其余学制
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Integer schsystemId : targetSystems) {
|
||||||
|
// 年级→入学年份(当前学年-年级偏移,与班级导入 getNowYearGradeMap 同口径);再查字典别称
|
||||||
|
Map<Integer, Integer> yearMap = baseSchsystemGradeService.getNowYearGradeMap(schId, schsystemId);
|
||||||
|
if (yearMap == null || yearMap.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Map<Integer, String> nameMap = baseSchsystemGradeService.getGradeNameMap(schId, schsystemId, null);
|
||||||
|
for (Map.Entry<Integer, Integer> e : yearMap.entrySet()) {
|
||||||
|
if (year.equals(e.getValue()) && nameMap.get(e.getKey()) != null) {
|
||||||
|
OapiGradeNameVO vo = new OapiGradeNameVO();
|
||||||
|
vo.setGradeId(e.getKey());
|
||||||
|
vo.setGradeName(nameMap.get(e.getKey()));
|
||||||
|
vo.setYear(year);
|
||||||
|
return ResponseEntity.ok(vo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量转 VO:按学校分组后统一回填学校名/年级名/班主任姓名 */
|
||||||
|
private List<OapiClassVO> toVOList(List<SchClass> classes) {
|
||||||
|
if (classes == null || classes.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
Map<Integer, List<SchClass>> bySchool = classes.stream()
|
||||||
|
.filter(c -> c.getSchId() != null)
|
||||||
|
.collect(Collectors.groupingBy(SchClass::getSchId, LinkedHashMap::new, Collectors.toList()));
|
||||||
|
|
||||||
|
List<OapiClassVO> result = new ArrayList<>();
|
||||||
|
for (Map.Entry<Integer, List<SchClass>> entry : bySchool.entrySet()) {
|
||||||
|
Integer schId = entry.getKey();
|
||||||
|
String schName = schInfoRepository.findBySchId(schId).map(s -> s.getName()).orElse(null);
|
||||||
|
Map<Integer, String> gradeNameMap = baseSchsystemGradeService.getGradeNameMap(schId, null, null);
|
||||||
|
Map<Integer, String> teacherNameMap = schTeacherService.toIdAndNameMap(schId);
|
||||||
|
for (SchClass clazz : entry.getValue()) {
|
||||||
|
result.add(toVO(clazz, schName, gradeNameMap, teacherNameMap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 无学校ID的脏数据兜底转换
|
||||||
|
classes.stream()
|
||||||
|
.filter(c -> c.getSchId() == null)
|
||||||
|
.forEach(c -> result.add(toVO(c, null, Collections.emptyMap(), Collections.emptyMap())));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private OapiClassVO toVO(SchClass clazz, String schName,
|
||||||
|
Map<Integer, String> gradeNameMap, Map<Integer, String> teacherNameMap) {
|
||||||
|
OapiClassVO vo = new OapiClassVO();
|
||||||
|
vo.setId(clazz.getId());
|
||||||
|
vo.setClassId(clazz.getClassId());
|
||||||
|
vo.setCode(clazz.getCode());
|
||||||
|
vo.setName(clazz.getName());
|
||||||
|
vo.setCname(clazz.getCname());
|
||||||
|
vo.setGradeId(clazz.getGradeId());
|
||||||
|
vo.setGradeName(clazz.getGradeId() == null ? null : gradeNameMap.get(clazz.getGradeId()));
|
||||||
|
vo.setSchId(clazz.getSchId());
|
||||||
|
vo.setSchName(schName);
|
||||||
|
vo.setFirstTeacherId(clazz.getFirstTeacherId());
|
||||||
|
vo.setFirstTeacherName(clazz.getFirstTeacherId() == null ? null : teacherNameMap.get(clazz.getFirstTeacherId()));
|
||||||
|
vo.setSecondTeacherId(clazz.getSecondTeacherId());
|
||||||
|
vo.setSecondTeacherName(clazz.getSecondTeacherId() == null ? null : teacherNameMap.get(clazz.getSecondTeacherId()));
|
||||||
|
vo.setCampusId(clazz.getCampusId());
|
||||||
|
vo.setEnrollYear(clazz.getEnrollYear());
|
||||||
|
vo.setGraduate(clazz.getGraduate());
|
||||||
|
vo.setSort(clazz.getSort());
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
}
|
||||||
+309
@@ -0,0 +1,309 @@
|
|||||||
|
package com.yunhai.bschclassmanage.controller.oapi.sch;
|
||||||
|
|
||||||
|
import com.yunhai.bschclassmanage.Repository.SchTeacherCustomGroupInGradeRepository;
|
||||||
|
import com.yunhai.bschclassmanage.Repository.SchTeacherCustomGroupInSubjectRepository;
|
||||||
|
import com.yunhai.bschclassmanage.Repository.SchTeacherCustomGroupInTeacherRepository;
|
||||||
|
import com.yunhai.bschclassmanage.Repository.SchTeacherCustomGroupRepository;
|
||||||
|
import com.yunhai.bschclassmanage.config.Config;
|
||||||
|
import com.yunhai.bschclassmanage.entity.SchTeacher;
|
||||||
|
import com.yunhai.bschclassmanage.entity.SchTeacherCustomGroup;
|
||||||
|
import com.yunhai.bschclassmanage.entity.SchTeacherCustomGroupInGrade;
|
||||||
|
import com.yunhai.bschclassmanage.entity.SchTeacherCustomGroupInSubject;
|
||||||
|
import com.yunhai.bschclassmanage.entity.SchTeacherCustomGroupInTeacher;
|
||||||
|
import com.yunhai.bschclassmanage.enums.CustomGroupType;
|
||||||
|
import com.yunhai.bschclassmanage.model.oa.TeaTreeVO;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.SelectIntVO;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.SelectStrVO;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.sch.OapiGroupVO;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.sch.TeaCustomGroupTreeVO;
|
||||||
|
import com.yunhai.bschclassmanage.service.IBaseSchsystemGradeService;
|
||||||
|
import com.yunhai.bschclassmanage.service.IComSchTreeService;
|
||||||
|
import com.yunhai.bschclassmanage.service.ISchDicSubjectService;
|
||||||
|
import com.yunhai.bschclassmanage.service.ISchTeacherService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Tag(name = "OapiSchGroup", description = "开放接口-学校组(教师组树/学生组树/年级组教研组等自定义组树)")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(Config.OAPI + "/sch/")
|
||||||
|
@CrossOrigin(originPatterns = {"*"}, allowCredentials = "true")
|
||||||
|
public class OapiSchGroupController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IComSchTreeService comSchTreeService;
|
||||||
|
@Autowired
|
||||||
|
private ISchTeacherService schTeacherService;
|
||||||
|
@Autowired
|
||||||
|
private IBaseSchsystemGradeService baseSchsystemGradeService;
|
||||||
|
@Autowired
|
||||||
|
private ISchDicSubjectService schDicSubjectService;
|
||||||
|
@Autowired
|
||||||
|
private SchTeacherCustomGroupRepository customGroupRepository;
|
||||||
|
@Autowired
|
||||||
|
private SchTeacherCustomGroupInGradeRepository groupInGradeRepository;
|
||||||
|
@Autowired
|
||||||
|
private SchTeacherCustomGroupInSubjectRepository groupInSubjectRepository;
|
||||||
|
@Autowired
|
||||||
|
private SchTeacherCustomGroupInTeacherRepository groupInTeacherRepository;
|
||||||
|
|
||||||
|
@GetMapping("getTeaGroupTree")
|
||||||
|
@Operation(summary = "获取教师组树+教师", description = "学校→教师组(含子组)→教师;toUser=false 时仅返回组骨架。与 oa/SchGroup/GetTeaTree 同源同结构")
|
||||||
|
public ResponseEntity<List<TeaTreeVO>> getTeaGroupTree(@RequestParam(value = "schId", required = false) Integer schId,
|
||||||
|
@RequestParam(value = "toUser", required = false, defaultValue = "true") boolean toUser) {
|
||||||
|
if (schId == null) {
|
||||||
|
return ResponseEntity.ok(Collections.emptyList());
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(comSchTreeService.getSchTeaTree(schId, toUser));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("getStuGroupTree")
|
||||||
|
@Operation(summary = "获取学生组树+学生", description = "学校→学段→年级→班级→学生;toUser=false 时仅返回组织骨架。与 oa/SchGroup/GetStuTree 同源同结构")
|
||||||
|
public ResponseEntity<List<TeaTreeVO>> getStuGroupTree(@RequestParam(value = "schId", required = false) Integer schId,
|
||||||
|
@RequestParam(value = "toUser", required = false, defaultValue = "true") boolean toUser) {
|
||||||
|
if (schId == null) {
|
||||||
|
return ResponseEntity.ok(Collections.emptyList());
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(comSchTreeService.getSchStuTree(schId, toUser));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("getTeaCustomGroupTree")
|
||||||
|
@Operation(summary = "获取教师自定义组树+教师(年级组/教研组/备课组)",
|
||||||
|
description = "不传 groupType 时返回三层:类型根节点(id=-1年级组/-2教研组/-3备课组)→ 组节点 → 教师叶子;"
|
||||||
|
+ "传 groupType(1年级组 2教研组 3备课组,教研组即教师学科组)时直接返回该类型组节点列表(两层)。"
|
||||||
|
+ "组节点携带组长、关联年级、关联学科;教师叶子携带 isLeader")
|
||||||
|
public ResponseEntity<List<TeaCustomGroupTreeVO>> getTeaCustomGroupTree(@RequestParam(value = "schId") Integer schId,
|
||||||
|
@RequestParam(value = "groupType", required = false) Integer groupType) {
|
||||||
|
if (schId == null) {
|
||||||
|
return ResponseEntity.ok(Collections.emptyList());
|
||||||
|
}
|
||||||
|
List<SchTeacherCustomGroup> groups = (groupType != null)
|
||||||
|
? customGroupRepository.findBySchIdAndGroupType(schId, groupType)
|
||||||
|
: customGroupRepository.findBySchId(schId);
|
||||||
|
if (groups == null || groups.isEmpty()) {
|
||||||
|
return ResponseEntity.ok(Collections.emptyList());
|
||||||
|
}
|
||||||
|
groups = groups.stream()
|
||||||
|
.sorted(Comparator.comparing(SchTeacherCustomGroup::getSort, Comparator.nullsLast(Integer::compareTo)))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return ResponseEntity.ok(buildTree(schId, groups, groupType != null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("getTeaCustomGroupList")
|
||||||
|
@Operation(summary = "获取教师分组列表(组/组员/组长合一)",
|
||||||
|
description = "按学校查教师分组列表:组id、组名称、学科集合、年级集合、组员集合、组长集合;"
|
||||||
|
+ "groupType 可选(1年级组 2教研组 3备课组,不传返回全部类型)。"
|
||||||
|
+ "组长取关系表 group_type=1 的全部教师(取不到时兜底组记录 leader_id 字段),组员取 group_type=2 且排除组长")
|
||||||
|
public ResponseEntity<List<OapiGroupVO>> getTeaCustomGroupList(@RequestParam(value = "schId") Integer schId,
|
||||||
|
@RequestParam(value = "groupType", required = false) Integer groupType) {
|
||||||
|
if (schId == null) {
|
||||||
|
return ResponseEntity.ok(Collections.emptyList());
|
||||||
|
}
|
||||||
|
List<SchTeacherCustomGroup> groups = (groupType != null)
|
||||||
|
? customGroupRepository.findBySchIdAndGroupType(schId, groupType)
|
||||||
|
: customGroupRepository.findBySchId(schId);
|
||||||
|
if (groups == null || groups.isEmpty()) {
|
||||||
|
return ResponseEntity.ok(Collections.emptyList());
|
||||||
|
}
|
||||||
|
groups = groups.stream()
|
||||||
|
.sorted(Comparator.comparing(SchTeacherCustomGroup::getSort, Comparator.nullsLast(Integer::compareTo)))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<Integer> groupIds = groups.stream().map(SchTeacherCustomGroup::getId).collect(Collectors.toList());
|
||||||
|
Map<Integer, List<SchTeacherCustomGroupInGrade>> groupIdToGrades = groupInGradeRepository.findByCgGroupIdIn(groupIds).stream()
|
||||||
|
.collect(Collectors.groupingBy(SchTeacherCustomGroupInGrade::getCgGroupId));
|
||||||
|
Map<Integer, List<SchTeacherCustomGroupInSubject>> groupIdToSubjects = groupInSubjectRepository.findByCgGroupIdIn(groupIds).stream()
|
||||||
|
.collect(Collectors.groupingBy(SchTeacherCustomGroupInSubject::getCgGroupId));
|
||||||
|
// relationType:1组长 2组员
|
||||||
|
Map<Integer, List<SchTeacherCustomGroupInTeacher>> groupIdToTeachers = groupInTeacherRepository.findByCgGroupIdIn(groupIds).stream()
|
||||||
|
.filter(r -> r.getTeaId() != null)
|
||||||
|
.collect(Collectors.groupingBy(SchTeacherCustomGroupInTeacher::getCgGroupId));
|
||||||
|
|
||||||
|
Map<Integer, SchTeacher> teacherMap = schTeacherService.getSchTeacherCacheList(schId).stream()
|
||||||
|
.filter(t -> t.getTeaId() != null)
|
||||||
|
.collect(Collectors.toMap(SchTeacher::getTeaId, t -> t, (a, b) -> a));
|
||||||
|
Map<Integer, String> gradeNameMap = baseSchsystemGradeService.getGradeNameMap(schId, null, null);
|
||||||
|
Map<String, String> subjectNameMap = schDicSubjectService.toIdAndNameMap(schId, null);
|
||||||
|
|
||||||
|
List<OapiGroupVO> result = new ArrayList<>(groups.size());
|
||||||
|
for (SchTeacherCustomGroup group : groups) {
|
||||||
|
List<SchTeacherCustomGroupInTeacher> relations = groupIdToTeachers.getOrDefault(group.getId(), Collections.emptyList());
|
||||||
|
List<Integer> leaderIds = resolveLeaders(group, relations);
|
||||||
|
|
||||||
|
OapiGroupVO vo = new OapiGroupVO();
|
||||||
|
vo.setGroupId(group.getId());
|
||||||
|
vo.setGroupName(group.getGroupName());
|
||||||
|
vo.setGroupType(group.getGroupType());
|
||||||
|
vo.setGroupTypeName(group.getGroupType() == null ? null : CustomGroupType.nameById(group.getGroupType()));
|
||||||
|
vo.setSubjects(groupIdToSubjects.getOrDefault(group.getId(), Collections.emptyList()).stream()
|
||||||
|
.map(SchTeacherCustomGroupInSubject::getSubId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.distinct()
|
||||||
|
.map(subId -> new SelectStrVO(subId, subjectNameMap.get(subId)))
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
vo.setGrades(groupIdToGrades.getOrDefault(group.getId(), Collections.emptyList()).stream()
|
||||||
|
.map(SchTeacherCustomGroupInGrade::getGradeId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.distinct()
|
||||||
|
.map(gradeId -> new SelectIntVO(gradeId, gradeNameMap.get(gradeId)))
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
vo.setLeaders(toIdNameList(leaderIds, teacherMap));
|
||||||
|
vo.setMembers(toIdNameList(relations.stream()
|
||||||
|
.filter(r -> Integer.valueOf(2).equals(r.getGroupType()))
|
||||||
|
.map(SchTeacherCustomGroupInTeacher::getTeaId)
|
||||||
|
.filter(id -> !leaderIds.contains(id))
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList()), teacherMap));
|
||||||
|
result.add(vo);
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 组长ID集合:关系表 group_type=1 的全部教师,取不到时兜底组记录 leader_id 字段(与 getSchCustomGroup 语义一致) */
|
||||||
|
private List<Integer> resolveLeaders(SchTeacherCustomGroup group, List<SchTeacherCustomGroupInTeacher> relations) {
|
||||||
|
List<Integer> leaderIds = relations.stream()
|
||||||
|
.filter(r -> Integer.valueOf(1).equals(r.getGroupType()))
|
||||||
|
.map(SchTeacherCustomGroupInTeacher::getTeaId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (leaderIds.isEmpty() && group.getLeaderId() != null) {
|
||||||
|
leaderIds = Collections.singletonList(group.getLeaderId());
|
||||||
|
}
|
||||||
|
return leaderIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 教师ID集合 → [{id,姓名}],按入参顺序;姓名取不到时与 getSchCustomGroup 一致回退"未知教师" */
|
||||||
|
private List<SelectIntVO> toIdNameList(List<Integer> teaIds, Map<Integer, SchTeacher> teacherMap) {
|
||||||
|
return teaIds.stream()
|
||||||
|
.map(teaId -> new SelectIntVO(teaId,
|
||||||
|
Optional.ofNullable(teacherMap.get(teaId)).map(SchTeacher::getName).orElse("未知教师")))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 组装类型节点→组节点→教师叶子树(flatTypeRoot=true 时不建类型根,组节点直接作为顶层) */
|
||||||
|
private List<TeaCustomGroupTreeVO> buildTree(Integer schId, List<SchTeacherCustomGroup> groups, boolean flatTypeRoot) {
|
||||||
|
List<Integer> groupIds = groups.stream().map(SchTeacherCustomGroup::getId).collect(Collectors.toList());
|
||||||
|
|
||||||
|
Map<Integer, List<Integer>> groupIdToGradeIds = groupInGradeRepository.findByCgGroupIdIn(groupIds).stream()
|
||||||
|
.collect(Collectors.groupingBy(SchTeacherCustomGroupInGrade::getCgGroupId,
|
||||||
|
Collectors.mapping(SchTeacherCustomGroupInGrade::getGradeId, Collectors.toList())));
|
||||||
|
Map<Integer, List<String>> groupIdToSubIds = groupInSubjectRepository.findByCgGroupIdIn(groupIds).stream()
|
||||||
|
.collect(Collectors.groupingBy(SchTeacherCustomGroupInSubject::getCgGroupId,
|
||||||
|
Collectors.mapping(SchTeacherCustomGroupInSubject::getSubId, Collectors.toList())));
|
||||||
|
// relationType:1组长 2组员
|
||||||
|
Map<Integer, List<SchTeacherCustomGroupInTeacher>> groupIdToTeachers = groupInTeacherRepository.findByCgGroupIdIn(groupIds).stream()
|
||||||
|
.filter(r -> r.getTeaId() != null)
|
||||||
|
.collect(Collectors.groupingBy(SchTeacherCustomGroupInTeacher::getCgGroupId));
|
||||||
|
|
||||||
|
Map<Integer, SchTeacher> teacherMap = schTeacherService.getSchTeacherCacheList(schId).stream()
|
||||||
|
.filter(t -> t.getTeaId() != null)
|
||||||
|
.collect(Collectors.toMap(SchTeacher::getTeaId, t -> t, (a, b) -> a));
|
||||||
|
Map<Integer, String> gradeNameMap = baseSchsystemGradeService.getGradeNameMap(schId, null, null);
|
||||||
|
Map<String, String> subjectNameMap = schDicSubjectService.toIdAndNameMap(schId, null);
|
||||||
|
|
||||||
|
Map<Integer, List<TeaCustomGroupTreeVO>> groupNodesByType = new LinkedHashMap<>();
|
||||||
|
for (SchTeacherCustomGroup group : groups) {
|
||||||
|
TeaCustomGroupTreeVO groupNode = buildGroupNode(group, flatTypeRoot,
|
||||||
|
groupIdToGradeIds, groupIdToSubIds, groupIdToTeachers, teacherMap, gradeNameMap, subjectNameMap);
|
||||||
|
groupNodesByType.computeIfAbsent(group.getGroupType(), k -> new ArrayList<>()).add(groupNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flatTypeRoot) {
|
||||||
|
return groupNodesByType.values().stream().flatMap(List::stream).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
// 类型根节点(id = -groupType),只输出实际有组的类型,按 1年级组 2教研组 3备课组 顺序
|
||||||
|
List<TeaCustomGroupTreeVO> roots = new ArrayList<>();
|
||||||
|
for (CustomGroupType type : CustomGroupType.list()) {
|
||||||
|
List<TeaCustomGroupTreeVO> children = groupNodesByType.get(type.getId());
|
||||||
|
if (children == null || children.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
TeaCustomGroupTreeVO typeNode = new TeaCustomGroupTreeVO();
|
||||||
|
typeNode.setId(-type.getId());
|
||||||
|
typeNode.setName(type.getName());
|
||||||
|
typeNode.setPid(0);
|
||||||
|
typeNode.setNo(type.getId());
|
||||||
|
typeNode.setType(1);
|
||||||
|
typeNode.setIsParent(true);
|
||||||
|
typeNode.setGroupType(type.getId());
|
||||||
|
typeNode.setGroupTypeName(type.getName());
|
||||||
|
typeNode.setChildren(children);
|
||||||
|
roots.add(typeNode);
|
||||||
|
}
|
||||||
|
return roots;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TeaCustomGroupTreeVO buildGroupNode(SchTeacherCustomGroup group, boolean flatTypeRoot,
|
||||||
|
Map<Integer, List<Integer>> groupIdToGradeIds,
|
||||||
|
Map<Integer, List<String>> groupIdToSubIds,
|
||||||
|
Map<Integer, List<SchTeacherCustomGroupInTeacher>> groupIdToTeachers,
|
||||||
|
Map<Integer, SchTeacher> teacherMap,
|
||||||
|
Map<Integer, String> gradeNameMap,
|
||||||
|
Map<String, String> subjectNameMap) {
|
||||||
|
TeaCustomGroupTreeVO node = new TeaCustomGroupTreeVO();
|
||||||
|
node.setId(group.getId());
|
||||||
|
node.setName(group.getGroupName());
|
||||||
|
node.setPid(flatTypeRoot ? 0 : (group.getGroupType() == null ? null : -group.getGroupType()));
|
||||||
|
node.setNo(group.getSort());
|
||||||
|
node.setType(1);
|
||||||
|
node.setIsParent(true);
|
||||||
|
node.setGroupType(group.getGroupType());
|
||||||
|
node.setGroupTypeName(group.getGroupType() == null ? null : CustomGroupType.nameById(group.getGroupType()));
|
||||||
|
node.setLeaderId(group.getLeaderId());
|
||||||
|
node.setLeaderName(group.getLeaderId() == null ? null
|
||||||
|
: Optional.ofNullable(teacherMap.get(group.getLeaderId())).map(SchTeacher::getName).orElse(null));
|
||||||
|
|
||||||
|
List<Integer> gradeIds = groupIdToGradeIds.getOrDefault(group.getId(), Collections.emptyList());
|
||||||
|
node.setGradeIds(gradeIds);
|
||||||
|
node.setGradeNames(gradeIds.stream()
|
||||||
|
.map(gradeNameMap::get)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
|
||||||
|
List<String> subIds = groupIdToSubIds.getOrDefault(group.getId(), Collections.emptyList());
|
||||||
|
node.setSubjectIds(subIds);
|
||||||
|
node.setSubjectNames(subIds.stream()
|
||||||
|
.map(subjectNameMap::get)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
|
||||||
|
// 教师叶子:组长(relationType=1)在前,组员在后;同组内按 teaId 去重
|
||||||
|
List<SchTeacherCustomGroupInTeacher> relations = groupIdToTeachers.getOrDefault(group.getId(), Collections.emptyList());
|
||||||
|
List<TeaCustomGroupTreeVO> teacherNodes = new ArrayList<>();
|
||||||
|
Set<Integer> seenTeaIds = new HashSet<>();
|
||||||
|
relations.stream().filter(r -> Integer.valueOf(1).equals(r.getGroupType()))
|
||||||
|
.forEach(r -> addTeacherNode(teacherNodes, seenTeaIds, r, group.getId(), teacherMap));
|
||||||
|
relations.stream().filter(r -> !Integer.valueOf(1).equals(r.getGroupType()))
|
||||||
|
.forEach(r -> addTeacherNode(teacherNodes, seenTeaIds, r, group.getId(), teacherMap));
|
||||||
|
node.setChildren(teacherNodes);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTeacherNode(List<TeaCustomGroupTreeVO> teacherNodes, Set<Integer> seenTeaIds,
|
||||||
|
SchTeacherCustomGroupInTeacher relation, Integer groupId,
|
||||||
|
Map<Integer, SchTeacher> teacherMap) {
|
||||||
|
if (!seenTeaIds.add(relation.getTeaId())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SchTeacher teacher = teacherMap.get(relation.getTeaId());
|
||||||
|
if (teacher == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TeaCustomGroupTreeVO leaf = new TeaCustomGroupTreeVO();
|
||||||
|
leaf.setId(relation.getTeaId());
|
||||||
|
leaf.setName(teacher.getName());
|
||||||
|
leaf.setPid(groupId);
|
||||||
|
leaf.setNo(0);
|
||||||
|
leaf.setType(2);
|
||||||
|
leaf.setIsParent(false);
|
||||||
|
leaf.setIsLeader(Integer.valueOf(1).equals(relation.getGroupType()));
|
||||||
|
teacherNodes.add(leaf);
|
||||||
|
}
|
||||||
|
}
|
||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
package com.yunhai.bschclassmanage.controller.oapi.sch;
|
||||||
|
|
||||||
|
import com.yunhai.bschclassmanage.Repository.BaseSchsystemRepository;
|
||||||
|
import com.yunhai.bschclassmanage.Repository.SchInfoRepository;
|
||||||
|
import com.yunhai.bschclassmanage.config.Config;
|
||||||
|
import com.yunhai.bschclassmanage.entity.BaseSchsystem;
|
||||||
|
import com.yunhai.bschclassmanage.entity.BaseSchsystemGrade;
|
||||||
|
import com.yunhai.bschclassmanage.entity.SchInfo;
|
||||||
|
import com.yunhai.bschclassmanage.enums.AcademicStage;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.SelectIntVO;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.sch.GradeItemVO;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.sch.SchInfoDetailVO;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.sch.SchSystemDetailVO;
|
||||||
|
import com.yunhai.bschclassmanage.service.IBaseSchsystemGradeService;
|
||||||
|
import com.yunhai.bschclassmanage.service.ISchBinSchsystemService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Tag(name = "OapiSchInfo", description = "开放接口-学校信息(含学段/学制)")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(Config.OAPI + "/sch/")
|
||||||
|
@CrossOrigin(originPatterns = {"*"}, allowCredentials = "true")
|
||||||
|
public class OapiSchInfoController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SchInfoRepository schInfoRepository;
|
||||||
|
@Autowired
|
||||||
|
private BaseSchsystemRepository baseSchsystemRepository;
|
||||||
|
@Autowired
|
||||||
|
private ISchBinSchsystemService schBinSchsystemService;
|
||||||
|
@Autowired
|
||||||
|
private IBaseSchsystemGradeService baseSchsystemGradeService;
|
||||||
|
|
||||||
|
@GetMapping("getSchInfo")
|
||||||
|
@Operation(summary = "获取学校信息", description = "按 schId(用户中心学校组ID)查询学校信息,含学段/学制信息")
|
||||||
|
public ResponseEntity<SchInfoDetailVO> getSchInfo(@RequestParam(value = "schId", required = false) Integer schId) {
|
||||||
|
if (schId == null) {
|
||||||
|
return ResponseEntity.ok(null);
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(schInfoRepository.findBySchId(schId).map(this::toDetail).orElse(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("getSchInfoList")
|
||||||
|
@Operation(summary = "获取学校列表", description = "按 schIds(逗号分隔)查询学校列表,schIds 为空时返回全部学校,含学段/学制信息")
|
||||||
|
public ResponseEntity<List<SchInfoDetailVO>> getSchInfoList(@RequestParam(value = "schIds", required = false) String schIds) {
|
||||||
|
List<SchInfo> schools;
|
||||||
|
Set<Integer> idSet = parseInts(schIds);
|
||||||
|
if (idSet.isEmpty()) {
|
||||||
|
schools = schInfoRepository.findAll();
|
||||||
|
} else {
|
||||||
|
schools = schInfoRepository.findAll().stream()
|
||||||
|
.filter(s -> idSet.contains(s.getSchId()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(schools.stream().map(this::toDetail).collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 学校实体 → 详情 VO(拼装学段/学制信息) */
|
||||||
|
private SchInfoDetailVO toDetail(SchInfo schInfo) {
|
||||||
|
SchInfoDetailVO vo = new SchInfoDetailVO();
|
||||||
|
vo.setSchId(schInfo.getSchId());
|
||||||
|
vo.setSchName(schInfo.getName());
|
||||||
|
vo.setSchSerId(schInfo.getSchSerId());
|
||||||
|
vo.setSort(schInfo.getSort());
|
||||||
|
|
||||||
|
List<SelectIntVO> systemList = schBinSchsystemService.selectSchBinSchsystem(schInfo.getSchId());
|
||||||
|
if (systemList == null || systemList.isEmpty()) {
|
||||||
|
vo.setSchSystemList(Collections.emptyList());
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Integer> systemIds = systemList.stream().map(SelectIntVO::getId).collect(Collectors.toList());
|
||||||
|
Map<Integer, BaseSchsystem> systemMap = baseSchsystemRepository.findAllById(systemIds).stream()
|
||||||
|
.collect(Collectors.toMap(BaseSchsystem::getId, s -> s, (a, b) -> a));
|
||||||
|
|
||||||
|
vo.setSchSystemList(systemIds.stream()
|
||||||
|
.map(systemMap::get)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(system -> toSystemDetail(schInfo.getSchId(), system))
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 学制实体 → 学制 VO(含学段名称与该学制年级) */
|
||||||
|
private SchSystemDetailVO toSystemDetail(Integer schId, BaseSchsystem system) {
|
||||||
|
SchSystemDetailVO vo = new SchSystemDetailVO();
|
||||||
|
vo.setId(system.getId());
|
||||||
|
vo.setName(system.getName());
|
||||||
|
vo.setAcademicStage(system.getAcademicStage());
|
||||||
|
vo.setAcademicStageName(system.getAcademicStage() == null ? null : AcademicStage.nameById(system.getAcademicStage()));
|
||||||
|
vo.setStartGradeCode(system.getStartGradeCode());
|
||||||
|
vo.setEndGradeCode(system.getEndGradeCode());
|
||||||
|
vo.setGraduationLeng(system.getGraduationLeng());
|
||||||
|
vo.setType(system.getType());
|
||||||
|
|
||||||
|
List<BaseSchsystemGrade> gradeList = baseSchsystemGradeService.getSchGradeCacheList(schId, system.getId(), null);
|
||||||
|
vo.setGradeList(gradeList == null ? Collections.emptyList() : gradeList.stream()
|
||||||
|
.map(g -> {
|
||||||
|
GradeItemVO item = new GradeItemVO();
|
||||||
|
item.setGradeId(g.getGradeId());
|
||||||
|
item.setGradeNickname(g.getGradeNickname());
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<Integer> parseInts(String value) {
|
||||||
|
Set<Integer> result = new LinkedHashSet<>();
|
||||||
|
if (value == null || value.trim().isEmpty()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
for (String token : value.split(",")) {
|
||||||
|
try {
|
||||||
|
result.add(Integer.valueOf(token.trim()));
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
+111
@@ -0,0 +1,111 @@
|
|||||||
|
package com.yunhai.bschclassmanage.controller.oapi.sch;
|
||||||
|
|
||||||
|
import com.yunhai.bschclassmanage.Repository.SchClassRepository;
|
||||||
|
import com.yunhai.bschclassmanage.Repository.SchInfoRepository;
|
||||||
|
import com.yunhai.bschclassmanage.Repository.SchStudentRepository;
|
||||||
|
import com.yunhai.bschclassmanage.config.Config;
|
||||||
|
import com.yunhai.bschclassmanage.entity.SchClass;
|
||||||
|
import com.yunhai.bschclassmanage.entity.SchStudent;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.sch.OapiStudentVO;
|
||||||
|
import com.yunhai.bschclassmanage.service.IBaseSchsystemGradeService;
|
||||||
|
import com.yunhai.bschclassmanage.service.ISchClassService;
|
||||||
|
import com.yunhai.bschclassmanage.service.ISchStudentService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Tag(name = "OapiSchStudent", description = "开放接口-学生")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(Config.OAPI + "/sch/")
|
||||||
|
@CrossOrigin(originPatterns = {"*"}, allowCredentials = "true")
|
||||||
|
public class OapiSchStudentController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SchStudentRepository schStudentRepository;
|
||||||
|
@Autowired
|
||||||
|
private SchClassRepository schClassRepository;
|
||||||
|
@Autowired
|
||||||
|
private SchInfoRepository schInfoRepository;
|
||||||
|
@Autowired
|
||||||
|
private ISchStudentService schStudentService;
|
||||||
|
@Autowired
|
||||||
|
private ISchClassService schClassService;
|
||||||
|
@Autowired
|
||||||
|
private IBaseSchsystemGradeService baseSchsystemGradeService;
|
||||||
|
|
||||||
|
@GetMapping("getStudentInfo")
|
||||||
|
@Operation(summary = "获取学生信息", description = "按学生ID(用户中心用户ID)查询学生信息,含班级/年级/学校名称")
|
||||||
|
public ResponseEntity<OapiStudentVO> getStudentInfo(@RequestParam(value = "stuId", required = false) Integer stuId) {
|
||||||
|
if (stuId == null) {
|
||||||
|
return ResponseEntity.ok(null);
|
||||||
|
}
|
||||||
|
// 同一 stu_id 可能在多所学校各有记录,取第一条即可(不能按唯一查询)
|
||||||
|
return ResponseEntity.ok(schStudentRepository.findByStuId(stuId).stream()
|
||||||
|
.findFirst()
|
||||||
|
.map(stu -> toVOList(stu.getSchId(), Collections.singletonList(stu)).get(0))
|
||||||
|
.orElse(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("getClassStudentList")
|
||||||
|
@Operation(summary = "获取班级学生列表", description = "按学校ID+班级ID(班级组ID)查询班级学生列表(在读正常)")
|
||||||
|
public ResponseEntity<List<OapiStudentVO>> getClassStudentList(@RequestParam(value = "schId", required = false) Integer schId,
|
||||||
|
@RequestParam(value = "classId", required = false) Integer classId) {
|
||||||
|
if (schId == null || classId == null) {
|
||||||
|
return ResponseEntity.ok(Collections.emptyList());
|
||||||
|
}
|
||||||
|
List<SchStudent> students = schStudentService.getSchGradeClassStudentCacheList(schId, null, null, classId);
|
||||||
|
return ResponseEntity.ok(toVOList(schId, students));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("getSchStudentList")
|
||||||
|
@Operation(summary = "获取全校学生列表", description = "按学校ID查询全校学生列表(在读正常)")
|
||||||
|
public ResponseEntity<List<OapiStudentVO>> getSchStudentList(@RequestParam(value = "schId", required = false) Integer schId) {
|
||||||
|
if (schId == null) {
|
||||||
|
return ResponseEntity.ok(Collections.emptyList());
|
||||||
|
}
|
||||||
|
List<SchStudent> students = schStudentService.getSchGradeClassStudentCacheList(schId, null, null, null);
|
||||||
|
return ResponseEntity.ok(toVOList(schId, students));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量转 VO:统一回填学校/年级/班级名称 */
|
||||||
|
private List<OapiStudentVO> toVOList(Integer schId, List<SchStudent> students) {
|
||||||
|
if (students == null || students.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
String schName = schId == null ? null
|
||||||
|
: schInfoRepository.findBySchId(schId).map(s -> s.getName()).orElse(null);
|
||||||
|
Map<Integer, String> gradeNameMap = schId == null ? Collections.emptyMap()
|
||||||
|
: baseSchsystemGradeService.getGradeNameMap(schId, null, null);
|
||||||
|
Map<Integer, SchClass> classMap = schId == null ? Collections.emptyMap()
|
||||||
|
: schClassService.getSchAllClassCacheList(schId).stream()
|
||||||
|
.filter(c -> c.getClassId() != null)
|
||||||
|
.collect(Collectors.toMap(SchClass::getClassId, c -> c, (a, b) -> a));
|
||||||
|
|
||||||
|
return students.stream().map(stu -> {
|
||||||
|
OapiStudentVO vo = new OapiStudentVO();
|
||||||
|
vo.setId(stu.getId());
|
||||||
|
vo.setStuId(stu.getStuId());
|
||||||
|
vo.setAccount(stu.getAccount());
|
||||||
|
vo.setName(stu.getName());
|
||||||
|
vo.setGender(stu.getGender());
|
||||||
|
vo.setStuClassNo(stu.getStuClassNo());
|
||||||
|
vo.setClassId(stu.getClassId());
|
||||||
|
vo.setStatus(stu.getStatus());
|
||||||
|
vo.setSchId(schId);
|
||||||
|
vo.setSchName(schName);
|
||||||
|
SchClass clazz = stu.getClassId() == null ? null : classMap.get(stu.getClassId());
|
||||||
|
if (clazz != null) {
|
||||||
|
vo.setClassName(clazz.getCname() == null || clazz.getCname().trim().isEmpty()
|
||||||
|
? clazz.getName() : clazz.getCname());
|
||||||
|
vo.setGradeId(clazz.getGradeId());
|
||||||
|
}
|
||||||
|
vo.setGradeName(vo.getGradeId() == null ? null : gradeNameMap.get(vo.getGradeId()));
|
||||||
|
return vo;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
package com.yunhai.bschclassmanage.controller.oapi.sch;
|
||||||
|
|
||||||
|
import com.yunhai.bschclassmanage.config.Config;
|
||||||
|
import com.yunhai.bschclassmanage.entity.SchDicSubject;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.sch.OapiSubjectVO;
|
||||||
|
import com.yunhai.bschclassmanage.service.ISchDicSubjectService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Tag(name = "OapiSchSubject", description = "开放接口-学科")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(Config.OAPI + "/sch/")
|
||||||
|
@CrossOrigin(originPatterns = {"*"}, allowCredentials = "true")
|
||||||
|
public class OapiSchSubjectController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ISchDicSubjectService schDicSubjectService;
|
||||||
|
|
||||||
|
@GetMapping("getSubjectInfo")
|
||||||
|
@Operation(summary = "获取学科信息", description = "按学校ID+学科ID(sch_dic_subject.id 主键,兼容传学科编码 code)查询单个学科")
|
||||||
|
public ResponseEntity<OapiSubjectVO> getSubjectInfo(@RequestParam(value = "schId", required = false) Integer schId,
|
||||||
|
@RequestParam(value = "subjectId", required = false) String subjectId) {
|
||||||
|
if (schId == null || subjectId == null || subjectId.trim().isEmpty()) {
|
||||||
|
return ResponseEntity.ok(null);
|
||||||
|
}
|
||||||
|
String code = subjectId.trim();
|
||||||
|
return schDicSubjectService.getSchSubList(schId, null).stream()
|
||||||
|
.filter(s -> code.equals(String.valueOf(s.getId())) || code.equals(s.getCode()))
|
||||||
|
.findFirst()
|
||||||
|
.map(this::toVO)
|
||||||
|
.map(ResponseEntity::ok)
|
||||||
|
.orElse(ResponseEntity.ok(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("getSchSubjectList")
|
||||||
|
@Operation(summary = "获取学校学科列表", description = "按学校ID查询学科列表,可传 schSystemId 按学制过滤")
|
||||||
|
public ResponseEntity<List<OapiSubjectVO>> getSchSubjectList(@RequestParam(value = "schId", required = false) Integer schId,
|
||||||
|
@RequestParam(value = "schSystemId", required = false) Integer schSystemId) {
|
||||||
|
if (schId == null) {
|
||||||
|
return ResponseEntity.ok(Collections.emptyList());
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(schDicSubjectService.getSchSubList(schId, schSystemId).stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(this::toVO)
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private OapiSubjectVO toVO(SchDicSubject subject) {
|
||||||
|
OapiSubjectVO vo = new OapiSubjectVO();
|
||||||
|
vo.setId(subject.getId());
|
||||||
|
vo.setCode(subject.getCode());
|
||||||
|
vo.setName(subject.getName());
|
||||||
|
vo.setSchSystemId(subject.getSchSystemId());
|
||||||
|
vo.setEnable(subject.getEnable());
|
||||||
|
vo.setSource(subject.getSource());
|
||||||
|
vo.setSort(subject.getSort());
|
||||||
|
vo.setSchId(subject.getSchId());
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
}
|
||||||
+116
@@ -0,0 +1,116 @@
|
|||||||
|
package com.yunhai.bschclassmanage.controller.oapi.sch;
|
||||||
|
|
||||||
|
import com.yunhai.bschclassmanage.Repository.SchClassTeachRepository;
|
||||||
|
import com.yunhai.bschclassmanage.config.Config;
|
||||||
|
import com.yunhai.bschclassmanage.entity.SchClass;
|
||||||
|
import com.yunhai.bschclassmanage.entity.SchClassTeach;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.sch.OapiTeachVO;
|
||||||
|
import com.yunhai.bschclassmanage.service.IBaseSchsystemGradeService;
|
||||||
|
import com.yunhai.bschclassmanage.service.ISchClassService;
|
||||||
|
import com.yunhai.bschclassmanage.service.ISchClassTeachService;
|
||||||
|
import com.yunhai.bschclassmanage.service.ISchDicSubjectService;
|
||||||
|
import com.yunhai.bschclassmanage.service.ISchTeacherService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Tag(name = "OapiSchTeach", description = "开放接口-班级任教")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(Config.OAPI + "/sch/")
|
||||||
|
@CrossOrigin(originPatterns = {"*"}, allowCredentials = "true")
|
||||||
|
public class OapiSchTeachController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SchClassTeachRepository schClassTeachRepository;
|
||||||
|
@Autowired
|
||||||
|
private ISchClassTeachService schClassTeachService;
|
||||||
|
@Autowired
|
||||||
|
private ISchClassService schClassService;
|
||||||
|
@Autowired
|
||||||
|
private ISchTeacherService schTeacherService;
|
||||||
|
@Autowired
|
||||||
|
private ISchDicSubjectService schDicSubjectService;
|
||||||
|
@Autowired
|
||||||
|
private IBaseSchsystemGradeService baseSchsystemGradeService;
|
||||||
|
|
||||||
|
@GetMapping("getClassTeachList")
|
||||||
|
@Operation(summary = "获取班级任教列表", description = "按学校ID+班级ID(班级组ID)查询班级任教的教师与学科")
|
||||||
|
public ResponseEntity<List<OapiTeachVO>> getClassTeachList(@RequestParam(value = "schId", required = false) Integer schId,
|
||||||
|
@RequestParam(value = "classId", required = false) Integer classId) {
|
||||||
|
if (schId == null || classId == null) {
|
||||||
|
return ResponseEntity.ok(Collections.emptyList());
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(toVOList(schId, schClassTeachService.getSchClassTeachCacheList(schId, null, classId)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("getTeacherTeachList")
|
||||||
|
@Operation(summary = "获取教师任教列表", description = "按教师ID(teaId)查询任教记录;可传 schId 限定学校(走缓存),不传则跨校查询")
|
||||||
|
public ResponseEntity<List<OapiTeachVO>> getTeacherTeachList(@RequestParam(value = "teaId", required = false) Integer teaId,
|
||||||
|
@RequestParam(value = "schId", required = false) Integer schId) {
|
||||||
|
if (teaId == null) {
|
||||||
|
return ResponseEntity.ok(Collections.emptyList());
|
||||||
|
}
|
||||||
|
if (schId != null) {
|
||||||
|
List<SchClassTeach> teaches = schClassTeachService.getSchClassTeachCacheList(schId, null, null).stream()
|
||||||
|
.filter(t -> teaId.equals(t.getTeaId()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return ResponseEntity.ok(toVOList(schId, teaches));
|
||||||
|
}
|
||||||
|
Map<Integer, List<SchClassTeach>> bySchool = schClassTeachRepository.findAll().stream()
|
||||||
|
.filter(t -> teaId.equals(t.getTeaId()) && t.getSchId() != null)
|
||||||
|
.collect(Collectors.groupingBy(SchClassTeach::getSchId, LinkedHashMap::new, Collectors.toList()));
|
||||||
|
List<OapiTeachVO> result = new ArrayList<>();
|
||||||
|
bySchool.forEach((schoolId, teaches) -> result.addAll(toVOList(schoolId, teaches)));
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("getSchTeachList")
|
||||||
|
@Operation(summary = "获取全校任教列表", description = "按学校ID查询全校任教记录(未毕业班级)")
|
||||||
|
public ResponseEntity<List<OapiTeachVO>> getSchTeachList(@RequestParam(value = "schId", required = false) Integer schId) {
|
||||||
|
if (schId == null) {
|
||||||
|
return ResponseEntity.ok(Collections.emptyList());
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(toVOList(schId, schClassTeachService.getSchClassTeachCacheList(schId, null, null)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量转 VO:按学校统一回填教师姓名/学科名称/班级名称/年级名称 */
|
||||||
|
private List<OapiTeachVO> toVOList(Integer schId, List<SchClassTeach> teaches) {
|
||||||
|
if (teaches == null || teaches.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
Map<Integer, String> teacherNameMap = schTeacherService.toIdAndNameMap(schId);
|
||||||
|
Map<String, String> subjectNameMap = schDicSubjectService.toIdAndNameMap(schId, null);
|
||||||
|
Map<Integer, String> gradeNameMap = baseSchsystemGradeService.getGradeNameMap(schId, null, null);
|
||||||
|
Map<Integer, SchClass> classMap = schClassService.getSchAllClassCacheList(schId).stream()
|
||||||
|
.filter(c -> c.getClassId() != null)
|
||||||
|
.collect(Collectors.toMap(SchClass::getClassId, c -> c, (a, b) -> a));
|
||||||
|
|
||||||
|
return teaches.stream().map(t -> {
|
||||||
|
OapiTeachVO vo = new OapiTeachVO();
|
||||||
|
vo.setId(t.getId());
|
||||||
|
vo.setSchId(t.getSchId());
|
||||||
|
vo.setGradeId(t.getGradeId());
|
||||||
|
vo.setGradeName(t.getGradeId() == null ? null : gradeNameMap.get(t.getGradeId()));
|
||||||
|
vo.setClassId(t.getClassId());
|
||||||
|
vo.setTeaId(t.getTeaId());
|
||||||
|
vo.setTeaName(t.getTeaId() == null ? null : teacherNameMap.get(t.getTeaId()));
|
||||||
|
vo.setSubId(t.getSubId());
|
||||||
|
vo.setSubName(t.getSubId() == null ? null : subjectNameMap.get(t.getSubId()));
|
||||||
|
SchClass clazz = t.getClassId() == null ? null : classMap.get(t.getClassId());
|
||||||
|
if (clazz != null) {
|
||||||
|
vo.setClassName(clazz.getCname() == null || clazz.getCname().trim().isEmpty()
|
||||||
|
? clazz.getName() : clazz.getCname());
|
||||||
|
if (vo.getGradeId() == null) {
|
||||||
|
vo.setGradeId(clazz.getGradeId());
|
||||||
|
vo.setGradeName(clazz.getGradeId() == null ? null : gradeNameMap.get(clazz.getGradeId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vo;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
package com.yunhai.bschclassmanage.controller.oapi.sch;
|
||||||
|
|
||||||
|
import com.yunhai.bschclassmanage.config.Config;
|
||||||
|
import com.yunhai.bschclassmanage.model.dto.TeacherBaseInfoDTO;
|
||||||
|
import com.yunhai.bschclassmanage.service.impl.teacher.SchTeacherBaseInfoService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import javax.validation.Valid;
|
||||||
|
|
||||||
|
@Tag(name = "OapiSchTeacher", description = "开放接口-教师基础信息")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(Config.OAPI + "/sch/")
|
||||||
|
public class OapiSchTeacherController {
|
||||||
|
@Autowired private SchTeacherBaseInfoService service;
|
||||||
|
|
||||||
|
@GetMapping("getTeacherBaseInfo")
|
||||||
|
@Operation(summary = "获取教师基础信息", description = "姓名、性别、身份证、手机;teaId 为平台用户ID")
|
||||||
|
public ResponseEntity<TeacherBaseInfoDTO> get(@RequestParam Integer schId, @RequestParam Integer teaId) {
|
||||||
|
return ResponseEntity.ok(service.get(schId, teaId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("editTeacherBaseInfo")
|
||||||
|
@Operation(summary = "编辑教师基础信息并同步基础平台", description = "提交四项资料;手机、身份证可清空,性别0女1男")
|
||||||
|
public ResponseEntity<TeacherBaseInfoDTO> edit(@Valid @RequestBody TeacherBaseInfoDTO dto) {
|
||||||
|
return ResponseEntity.ok(service.edit(dto));
|
||||||
|
}
|
||||||
|
}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
package com.yunhai.bschclassmanage.controller.oapi.sch;
|
||||||
|
|
||||||
|
import com.yunhai.bschclassmanage.config.Config;
|
||||||
|
import com.yunhai.bschclassmanage.entity.BaseYearTerm;
|
||||||
|
import com.yunhai.bschclassmanage.model.tool.YearTerm;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.BaseYearTermVO;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.sch.NowYearTermVO;
|
||||||
|
import com.yunhai.bschclassmanage.service.IBaseYearTermService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Tag(name = "OapiYearTerm", description = "开放接口-学年学期")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(Config.OAPI + "/sch/")
|
||||||
|
@CrossOrigin(originPatterns = {"*"}, allowCredentials = "true")
|
||||||
|
public class OapiYearTermController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IBaseYearTermService baseYearTermService;
|
||||||
|
|
||||||
|
@GetMapping("getNowYearTerm")
|
||||||
|
@Operation(summary = "获取当前学年学期", description = "返回当前学年(nowYear=true)、当前学期 term(1第一学期 2第二学期)及当前学期起止日期")
|
||||||
|
public ResponseEntity<NowYearTermVO> getNowYearTerm() {
|
||||||
|
BaseYearTerm nowTerm = baseYearTermService.getNowYearTerm();
|
||||||
|
YearTerm current = baseYearTermService.getCurrentYearTerm();
|
||||||
|
return ResponseEntity.ok(toVO(nowTerm, current));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("getYearTermList")
|
||||||
|
@Operation(summary = "获取学年学期列表", description = "返回全部学年学期列表(按学年倒序),当前学年带 nowYear=true 标记")
|
||||||
|
public ResponseEntity<List<BaseYearTermVO>> getYearTermList() {
|
||||||
|
return ResponseEntity.ok(baseYearTermService.getAllYearTermVO());
|
||||||
|
}
|
||||||
|
|
||||||
|
private NowYearTermVO toVO(BaseYearTerm entity, YearTerm current) {
|
||||||
|
NowYearTermVO vo = new NowYearTermVO();
|
||||||
|
vo.setId(entity.getId());
|
||||||
|
vo.setYear(current != null && current.getYear() != null ? current.getYear() : entity.getYear());
|
||||||
|
vo.setTerm(current == null ? null : current.getTerm());
|
||||||
|
vo.setYearStartDate(entity.getYearStartDate());
|
||||||
|
vo.setYearEndDate(entity.getYearEndDate());
|
||||||
|
vo.setFirstTermStartDate(entity.getFirstTermStartDate());
|
||||||
|
vo.setFirstTermEndDate(entity.getFirstTermEndDate());
|
||||||
|
vo.setSecondTermStartDate(entity.getSecondTermStartDate());
|
||||||
|
vo.setSecondTermEndDate(entity.getSecondTermEndDate());
|
||||||
|
vo.setUpgradeDate(entity.getUpgradeDate());
|
||||||
|
vo.setTermStartDate(Integer.valueOf(2).equals(vo.getTerm()) ? entity.getSecondTermStartDate() : entity.getFirstTermStartDate());
|
||||||
|
vo.setTermEndDate(Integer.valueOf(2).equals(vo.getTerm()) ? entity.getSecondTermEndDate() : entity.getFirstTermEndDate());
|
||||||
|
vo.setNowYear(true);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
}
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
package com.yunhai.bschclassmanage.controller.oapi.sync;
|
||||||
|
|
||||||
|
import com.yunhai.bschclassmanage.annotation.SyncOpLog;
|
||||||
|
import com.yunhai.bschclassmanage.config.Config;
|
||||||
|
import com.yunhai.bschclassmanage.enums.SyncDataType;
|
||||||
|
import com.yunhai.bschclassmanage.model.tool.SyncClassDTO;
|
||||||
|
import com.yunhai.bschclassmanage.model.tool.SyncClassStudentDTO;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.sync.SyncClassResultVO;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.sync.SyncStudentResultVO;
|
||||||
|
import com.yunhai.bschclassmanage.service.ISyncClassService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分班结果推送开放接口(/oapi/class/,调用方:分班管理 AcaClassAssign)。
|
||||||
|
* <p>与既有 /oapi/group、/oapi/user 同步接口不同:本控制器用强类型 DTO 入参 + 结果 VO 出参
|
||||||
|
* (推送方需要拿到 本地班号→平台classId 映射与逐生成败结果,void 返回无法承载),
|
||||||
|
* 风格对齐 /oapi/sch 只读接口。</p>
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "OapiSyncClass", description = "开放接口-分班结果推送(班级/班级学生)")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(Config.OAPI + "/class/")
|
||||||
|
@CrossOrigin(originPatterns = {"*"}, allowCredentials = "true")
|
||||||
|
public class SyncClassController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ISyncClassService syncClassService;
|
||||||
|
|
||||||
|
// 返回 ResponseEntity 绕过平台 ApiResponseAdvice 包装(对齐 /oapi/sch 只读接口:成功裸 VO,业务异常走 ValidationExceptionHandler 输出 {code,msg})
|
||||||
|
@SyncOpLog(SyncDataType.SYNC_ADD_CLASS)
|
||||||
|
@PostMapping("AddClass")
|
||||||
|
@Operation(summary = "添加班级(同步组)", description = "学生组→[学段组]→年级组(入学年份)→班级组(年份+两位班号)+sch_class;按 code 幂等,返回平台 classId 映射")
|
||||||
|
public ResponseEntity<SyncClassResultVO> AddClass(@RequestBody SyncClassDTO dto) {
|
||||||
|
return ResponseEntity.ok(syncClassService.addClass(dto));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SyncOpLog(SyncDataType.SYNC_ADD_STU)
|
||||||
|
@PostMapping("AddClassStudent")
|
||||||
|
@Operation(summary = "添加班级学生(同步学生)", description = "用户中心建号+sch_student 落库;按 account/班内姓名幂等;默认聚合模式逐生返回成败,rollbackOnError=true 整批回滚")
|
||||||
|
public ResponseEntity<SyncStudentResultVO> AddClassStudent(@RequestBody SyncClassStudentDTO dto) {
|
||||||
|
return ResponseEntity.ok(syncClassService.addClassStudent(dto));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,9 @@ public enum SyncDataType
|
|||||||
SYNC_MOVE_USER("Sync-MoveUser","用户移组"),
|
SYNC_MOVE_USER("Sync-MoveUser","用户移组"),
|
||||||
SYNC_USER("Sync-SyncUser","全量用户"),
|
SYNC_USER("Sync-SyncUser","全量用户"),
|
||||||
|
|
||||||
|
SYNC_ADD_CLASS("Sync-AddClass","分班推送添加班级"),
|
||||||
|
SYNC_ADD_STU("Sync-AddClassStudent","分班推送添加班级学生"),
|
||||||
|
|
||||||
//推送
|
//推送
|
||||||
SEND_ADD_GROUP("Send-AddGroup","添加组"),
|
SEND_ADD_GROUP("Send-AddGroup","添加组"),
|
||||||
SEND_EDIT_GROUP("Send-EditGroup","编辑组"),
|
SEND_EDIT_GROUP("Send-EditGroup","编辑组"),
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.yunhai.bschclassmanage.model.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import javax.validation.constraints.*;
|
||||||
|
|
||||||
|
/** 教师基础资料;teaId 为基础平台用户 ID,而非本地教师表主键。 */
|
||||||
|
@Data
|
||||||
|
public class TeacherBaseInfoDTO {
|
||||||
|
@NotNull @Min(1)
|
||||||
|
private Integer schId;
|
||||||
|
@NotNull @Min(1)
|
||||||
|
private Integer teaId;
|
||||||
|
@NotBlank @Size(max = 255)
|
||||||
|
private String name;
|
||||||
|
/** 0=女,1=男;null=未登记 */
|
||||||
|
@Min(0) @Max(1)
|
||||||
|
private Integer gender;
|
||||||
|
@Pattern(regexp = "^$|^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dXx]$", message = "身份证号码格式不正确")
|
||||||
|
private String idcard;
|
||||||
|
@Size(max = 255)
|
||||||
|
private String phone;
|
||||||
|
}
|
||||||
@@ -1,30 +1,54 @@
|
|||||||
package com.yunhai.bschclassmanage.model.oa;
|
package com.yunhai.bschclassmanage.model.oa;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class TeaInfoVO {
|
public class TeaInfoVO {
|
||||||
|
@JsonProperty("SchSerID")
|
||||||
private String SchSerID;
|
private String SchSerID;
|
||||||
|
@JsonProperty("Teapolitical")
|
||||||
private String Teapolitical;
|
private String Teapolitical;
|
||||||
|
@JsonProperty("TeaWorkStart")
|
||||||
private String TeaWorkStart;
|
private String TeaWorkStart;
|
||||||
|
@JsonProperty("Account")
|
||||||
private String Account;
|
private String Account;
|
||||||
|
@JsonProperty("SchSerIDs")
|
||||||
private String SchSerIDs;
|
private String SchSerIDs;
|
||||||
|
@JsonProperty("role")
|
||||||
private Integer role;
|
private Integer role;
|
||||||
|
@JsonProperty("JiaoYuJu")
|
||||||
private String JiaoYuJu;
|
private String JiaoYuJu;
|
||||||
|
@JsonProperty("TeaSex")
|
||||||
private String TeaSex;
|
private String TeaSex;
|
||||||
|
@JsonProperty("IsLeadingMiddle")
|
||||||
private Integer IsLeadingMiddle;
|
private Integer IsLeadingMiddle;
|
||||||
|
@JsonProperty("TeaOriginPlace")
|
||||||
private String TeaOriginPlace;
|
private String TeaOriginPlace;
|
||||||
|
@JsonProperty("Photo")
|
||||||
private String Photo;
|
private String Photo;
|
||||||
|
@JsonProperty("TeaIDCard")
|
||||||
private String TeaIDCard;
|
private String TeaIDCard;
|
||||||
|
@JsonProperty("Name")
|
||||||
private String Name;
|
private String Name;
|
||||||
|
@JsonProperty("GroupID")
|
||||||
private Integer GroupID;
|
private Integer GroupID;
|
||||||
|
@JsonProperty("SchName")
|
||||||
private String SchName;
|
private String SchName;
|
||||||
|
@JsonProperty("TeaTeachStart")
|
||||||
private String TeaTeachStart;
|
private String TeaTeachStart;
|
||||||
|
@JsonProperty("TeaSerID")
|
||||||
private String TeaSerID;
|
private String TeaSerID;
|
||||||
|
@JsonProperty("UserID")
|
||||||
private Integer UserID;
|
private Integer UserID;
|
||||||
|
@JsonProperty("TeaNation")
|
||||||
private String TeaNation;
|
private String TeaNation;
|
||||||
|
@JsonProperty("Tel")
|
||||||
private String Tel;
|
private String Tel;
|
||||||
|
@JsonProperty("TeaWorkType")
|
||||||
private String TeaWorkType;
|
private String TeaWorkType;
|
||||||
|
@JsonProperty("TeaNo")
|
||||||
private Integer TeaNo;
|
private Integer TeaNo;
|
||||||
|
@JsonProperty("TeaBirth")
|
||||||
private String TeaBirth;
|
private String TeaBirth;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.yunhai.bschclassmanage.model.oa;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 教师组树节点(SchGroup/GetTeaTree 输出)
|
||||||
|
* type=1 组节点(含学校根节点),type=2 教师叶子节点
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class TeaTreeVO {
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
private Integer pid;
|
||||||
|
private Integer no;
|
||||||
|
private Integer type;
|
||||||
|
@JsonProperty("isParent")
|
||||||
|
private Boolean isParent;
|
||||||
|
private String photo = "";
|
||||||
|
private List<TeaTreeVO> children;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.yunhai.bschclassmanage.model.tool;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分班结果推送-添加班级入参 DTO(POST /oapi/class/AddClass)。
|
||||||
|
* 调用方为分班管理(AcaClassAssign),同步组:学生组→[学段组]→年级组→班级组 + sch_class。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class SyncClassDTO {
|
||||||
|
|
||||||
|
/** 学校id(=学校组id) */
|
||||||
|
private Integer schId;
|
||||||
|
|
||||||
|
/** 学段:AcademicStage 口径 1小学/2初中/3高中(分班方负责换算,非分班侧 2/3/4) */
|
||||||
|
private Integer stage;
|
||||||
|
|
||||||
|
/** 入学年份(=分班项目 year,如 2026)。显式传入,禁止用 NowYearTerm 推导(分班多在 6-8 月,会差一学年) */
|
||||||
|
private Integer enrollYear;
|
||||||
|
|
||||||
|
/** 班级列表 */
|
||||||
|
private List<ClassItem> classes;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class ClassItem {
|
||||||
|
|
||||||
|
/** 班号 1..N(=aca_assign_class.sort_order,决定 code 尾号与 sch_class.sort) */
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
/** 分班侧班名(可空;非空时写 cname 别名,name 统一按规则生成 sort+"班")。
|
||||||
|
* 分班侧口径(2026-10-21):仅语义班名(阳光班/苹果班)才传,序号生成的班名(1班/2班)不传,cname 留空 */
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 班主任(平台教师 UserTeaSerID,可空)→ first_teacher_id */
|
||||||
|
private Integer teacherId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.yunhai.bschclassmanage.model.tool;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分班结果推送-添加班级学生入参 DTO(POST /oapi/class/AddClassStudent)。
|
||||||
|
* 用户中心建号 + sch_student 落库;默认聚合模式(逐生返回成败),rollbackOnError=true 走整批回滚。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class SyncClassStudentDTO {
|
||||||
|
|
||||||
|
/** 学校id */
|
||||||
|
private Integer schId;
|
||||||
|
|
||||||
|
/** true=整批 all-or-nothing(任一失败回滚本批已建账号并整批报错);缺省 false=聚合模式 */
|
||||||
|
private Boolean rollbackOnError;
|
||||||
|
|
||||||
|
/** 学生列表(可跨班) */
|
||||||
|
private List<StudentItem> students;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public static class StudentItem {
|
||||||
|
|
||||||
|
/** 目标班级(班级组id,=AddClass 返回的 classId) */
|
||||||
|
private Integer classId;
|
||||||
|
|
||||||
|
/** 姓名(必填) */
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 性别:本接口口径 1男/0女(分班侧 1男/2女/0未知,由分班方换算后传入) */
|
||||||
|
private Integer gender;
|
||||||
|
|
||||||
|
/** 登录账号(可空;空则由用户中心默认逻辑处理) */
|
||||||
|
private String account;
|
||||||
|
|
||||||
|
/** 班内学号(同时决定 sort) */
|
||||||
|
private String stuClassNo;
|
||||||
|
|
||||||
|
/** 初始密码(可空=不带,走用户中心默认密码策略) */
|
||||||
|
private String pwd;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.yunhai.bschclassmanage.model.vo.sch;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学制年级项(oapi/sch 学校信息内的 gradeList 元素)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class GradeItemVO {
|
||||||
|
|
||||||
|
/** 年级编码(BaseSchsystemGrade.gradeId,与 sch_class.grade_id 同源) */
|
||||||
|
private Integer gradeId;
|
||||||
|
|
||||||
|
/** 年级别称 */
|
||||||
|
private String gradeNickname;
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.yunhai.bschclassmanage.model.vo.sch;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前学年学期(oapi/sch/getNowYearTerm 输出)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class NowYearTermVO {
|
||||||
|
|
||||||
|
/** 学年(如 2026) */
|
||||||
|
private Integer year;
|
||||||
|
|
||||||
|
/** 当前学期(1第一学期 2第二学期),按当前日期落在学期区间判定 */
|
||||||
|
private Integer term;
|
||||||
|
|
||||||
|
/** 学年学期记录ID(base_year_term.id) */
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||||
|
private Date yearStartDate;
|
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||||
|
private Date yearEndDate;
|
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||||
|
private Date firstTermStartDate;
|
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||||
|
private Date firstTermEndDate;
|
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||||
|
private Date secondTermStartDate;
|
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||||
|
private Date secondTermEndDate;
|
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||||
|
private Date upgradeDate;
|
||||||
|
|
||||||
|
/** 当前学期开始日期(按 term 取一/二学期开始日期) */
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||||
|
private Date termStartDate;
|
||||||
|
|
||||||
|
/** 当前学期结束日期(按 term 取一/二学期结束日期) */
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||||
|
private Date termEndDate;
|
||||||
|
|
||||||
|
/** 是否当前学年学期,恒为 true */
|
||||||
|
private Boolean nowYear;
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package com.yunhai.bschclassmanage.model.vo.sch;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班级信息(oapi/sch 班级接口输出),含班主任
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class OapiClassVO {
|
||||||
|
|
||||||
|
/** 班级记录ID(sch_class.id 主键) */
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
/** 班级ID(班级组ID group_class_id,学生/任教的 classId 同源) */
|
||||||
|
private Integer classId;
|
||||||
|
|
||||||
|
/** 班级编码 */
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
/** 班级名称 */
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 班级别称 */
|
||||||
|
private String cname;
|
||||||
|
|
||||||
|
/** 年级编码(base_sch_system_grade.grade_id) */
|
||||||
|
private Integer gradeId;
|
||||||
|
|
||||||
|
/** 年级名称 */
|
||||||
|
private String gradeName;
|
||||||
|
|
||||||
|
/** 学校ID */
|
||||||
|
private Integer schId;
|
||||||
|
|
||||||
|
/** 学校名称 */
|
||||||
|
private String schName;
|
||||||
|
|
||||||
|
/** 班主任ID(教师 teaId) */
|
||||||
|
private Integer firstTeacherId;
|
||||||
|
|
||||||
|
/** 班主任姓名 */
|
||||||
|
private String firstTeacherName;
|
||||||
|
|
||||||
|
/** 副班主任ID(教师 teaId) */
|
||||||
|
private Integer secondTeacherId;
|
||||||
|
|
||||||
|
/** 副班主任姓名 */
|
||||||
|
private String secondTeacherName;
|
||||||
|
|
||||||
|
/** 校区ID */
|
||||||
|
private Integer campusId;
|
||||||
|
|
||||||
|
/** 注册年份 */
|
||||||
|
private Integer enrollYear;
|
||||||
|
|
||||||
|
/** 是否毕业 */
|
||||||
|
private Boolean graduate;
|
||||||
|
|
||||||
|
/** 序号 */
|
||||||
|
private Integer sort;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.yunhai.bschclassmanage.model.vo.sch;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 年级名称 VO(开放接口 getGradeName 返回):学校年级字典名称 + 对应年级编码与入学年份。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class OapiGradeNameVO {
|
||||||
|
|
||||||
|
@Schema(description = "年级编码(K12 年级ID)")
|
||||||
|
private Integer gradeId;
|
||||||
|
|
||||||
|
@Schema(description = "年级名称(学校年级字典别称,如 小一/一年级/初一)")
|
||||||
|
private String gradeName;
|
||||||
|
|
||||||
|
@Schema(description = "入学年份")
|
||||||
|
private Integer year;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.yunhai.bschclassmanage.model.vo.sch;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.SelectIntVO;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.SelectStrVO;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 教师分组信息(oapi/sch/getTeaCustomGroupList 输出,对应教师分组管理页面的组/组员/组长三栏)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class OapiGroupVO {
|
||||||
|
|
||||||
|
/** 组ID(sch_teacher_custom_group.id) */
|
||||||
|
private Integer groupId;
|
||||||
|
|
||||||
|
/** 组名称 */
|
||||||
|
private String groupName;
|
||||||
|
|
||||||
|
/** 组类型(1年级组 2教研组 3备课组) */
|
||||||
|
private Integer groupType;
|
||||||
|
|
||||||
|
/** 组类型名称 */
|
||||||
|
private String groupTypeName;
|
||||||
|
|
||||||
|
/** 学科集合 [{id:学科编码, name:学科名称}] */
|
||||||
|
private List<SelectStrVO> subjects;
|
||||||
|
|
||||||
|
/** 年级集合 [{id:年级编码, name:年级名称}] */
|
||||||
|
private List<SelectIntVO> grades;
|
||||||
|
|
||||||
|
/** 组员集合 [{id:教师id, name:教师名称}](不含组长) */
|
||||||
|
private List<SelectIntVO> members;
|
||||||
|
|
||||||
|
/** 组长集合 [{id:教师id, name:教师名称}] */
|
||||||
|
private List<SelectIntVO> leaders;
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.yunhai.bschclassmanage.model.vo.sch;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学生信息(oapi/sch 学生接口输出)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class OapiStudentVO {
|
||||||
|
|
||||||
|
/** 学生记录ID(sch_student.id 主键) */
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
/** 学生ID(用户中心用户ID stu_id) */
|
||||||
|
private Integer stuId;
|
||||||
|
|
||||||
|
/** 学生账户 */
|
||||||
|
private String account;
|
||||||
|
|
||||||
|
/** 学生姓名 */
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 性别(0女 1男) */
|
||||||
|
private Integer gender;
|
||||||
|
|
||||||
|
/** 班级学号 */
|
||||||
|
private String stuClassNo;
|
||||||
|
|
||||||
|
/** 班级ID(班级组ID,与班级接口的 classId 同源) */
|
||||||
|
private Integer classId;
|
||||||
|
|
||||||
|
/** 班级名称 */
|
||||||
|
private String className;
|
||||||
|
|
||||||
|
/** 年级编码 */
|
||||||
|
private Integer gradeId;
|
||||||
|
|
||||||
|
/** 年级名称 */
|
||||||
|
private String gradeName;
|
||||||
|
|
||||||
|
/** 学校ID */
|
||||||
|
private Integer schId;
|
||||||
|
|
||||||
|
/** 学校名称 */
|
||||||
|
private String schName;
|
||||||
|
|
||||||
|
/** 状态(0正常 1禁用) */
|
||||||
|
private Integer status;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.yunhai.bschclassmanage.model.vo.sch;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学科信息(oapi/sch 学科接口输出)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class OapiSubjectVO {
|
||||||
|
|
||||||
|
/** 学科ID(sch_dic_subject.id 主键) */
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
/** 学科编码(任教/自定义组关联的 subId 即该编码) */
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
/** 学科名称 */
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 学制ID */
|
||||||
|
private Integer schSystemId;
|
||||||
|
|
||||||
|
/** 是否启用 */
|
||||||
|
private Boolean enable;
|
||||||
|
|
||||||
|
/** 数据来源(0系统 1学校) */
|
||||||
|
private Integer source;
|
||||||
|
|
||||||
|
/** 序号 */
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
/** 学校ID */
|
||||||
|
private Integer schId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.yunhai.bschclassmanage.model.vo.sch;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任教信息(oapi/sch 任教接口输出)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class OapiTeachVO {
|
||||||
|
|
||||||
|
/** 任教记录ID(sch_class_teach.id 主键) */
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
/** 学校ID */
|
||||||
|
private Integer schId;
|
||||||
|
|
||||||
|
/** 班级ID(班级组ID) */
|
||||||
|
private Integer classId;
|
||||||
|
|
||||||
|
/** 班级名称 */
|
||||||
|
private String className;
|
||||||
|
|
||||||
|
/** 年级编码 */
|
||||||
|
private Integer gradeId;
|
||||||
|
|
||||||
|
/** 年级名称 */
|
||||||
|
private String gradeName;
|
||||||
|
|
||||||
|
/** 教师ID(teaId) */
|
||||||
|
private Integer teaId;
|
||||||
|
|
||||||
|
/** 教师姓名 */
|
||||||
|
private String teaName;
|
||||||
|
|
||||||
|
/** 学科ID(sch_dic_subject.code) */
|
||||||
|
private String subId;
|
||||||
|
|
||||||
|
/** 学科名称 */
|
||||||
|
private String subName;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.yunhai.bschclassmanage.model.vo.sch;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校信息(oapi/sch/getSchInfo、getSchInfoList 输出),含学段/学制信息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class SchInfoDetailVO {
|
||||||
|
|
||||||
|
/** 学校ID(用户中心学校组ID,sch_info.sch_id) */
|
||||||
|
private Integer schId;
|
||||||
|
|
||||||
|
/** 学校名称 */
|
||||||
|
private String schName;
|
||||||
|
|
||||||
|
/** 学校序列号(sch_info.sch_serid) */
|
||||||
|
private String schSerId;
|
||||||
|
|
||||||
|
/** 排序号 */
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
/** 学段/学制信息(学校绑定的学制列表,每个学制含其年级) */
|
||||||
|
private List<SchSystemDetailVO> schSystemList;
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.yunhai.bschclassmanage.model.vo.sch;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学制信息(oapi/sch 学校信息内的 schSystemList 元素,含学段编码与该学制下的年级)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class SchSystemDetailVO {
|
||||||
|
|
||||||
|
/** 学制ID(base_sch_system.id) */
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
/** 学制名称 */
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 学段编码(0学前 1小学 2初中 3高中 4高等) */
|
||||||
|
private Integer academicStage;
|
||||||
|
|
||||||
|
/** 学段名称 */
|
||||||
|
private String academicStageName;
|
||||||
|
|
||||||
|
/** 开始年级编码 */
|
||||||
|
private Integer startGradeCode;
|
||||||
|
|
||||||
|
/** 结束年级编码 */
|
||||||
|
private Integer endGradeCode;
|
||||||
|
|
||||||
|
/** 毕业年长度 */
|
||||||
|
private Integer graduationLeng;
|
||||||
|
|
||||||
|
/** 教育类型(1基础教育 2职业教育) */
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
/** 该学制下的年级列表 */
|
||||||
|
private List<GradeItemVO> gradeList;
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.yunhai.bschclassmanage.model.vo.sch;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 教师自定义组树节点(oapi/sch/getTeaCustomGroupTree 输出)。
|
||||||
|
* 节点契约与 SchGroup/GetTeaTree 的 TeaTreeVO 一致:
|
||||||
|
* type=1 组节点(isParent=true),type=2 教师叶子(isParent=false、children 省略)。
|
||||||
|
* 组节点额外携带组类型/组长/年级/学科信息,教师叶子额外携带 isLeader。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class TeaCustomGroupTreeVO {
|
||||||
|
|
||||||
|
/** 组节点:组ID(sch_teacher_custom_group.id);类型节点:-1/-2/-3;教师叶子:teaId */
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 父节点ID(类型节点 pid=0) */
|
||||||
|
private Integer pid;
|
||||||
|
|
||||||
|
/** 排序号 */
|
||||||
|
private Integer no;
|
||||||
|
|
||||||
|
/** 1组节点 2教师叶子 */
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
@JsonProperty("isParent")
|
||||||
|
private Boolean isParent;
|
||||||
|
|
||||||
|
/** 组节点:组类型(1年级组 2教研组 3备课组) */
|
||||||
|
private Integer groupType;
|
||||||
|
|
||||||
|
/** 组节点:组类型名称 */
|
||||||
|
private String groupTypeName;
|
||||||
|
|
||||||
|
/** 组节点:组长ID(teaId) */
|
||||||
|
private Integer leaderId;
|
||||||
|
|
||||||
|
/** 组节点:组长姓名 */
|
||||||
|
private String leaderName;
|
||||||
|
|
||||||
|
/** 组节点:关联年级编码列表 */
|
||||||
|
private List<Integer> gradeIds;
|
||||||
|
|
||||||
|
/** 组节点:关联年级名称列表 */
|
||||||
|
private List<String> gradeNames;
|
||||||
|
|
||||||
|
/** 组节点:关联学科编码列表 */
|
||||||
|
private List<String> subjectIds;
|
||||||
|
|
||||||
|
/** 组节点:关联学科名称列表 */
|
||||||
|
private List<String> subjectNames;
|
||||||
|
|
||||||
|
/** 教师叶子:是否组长 */
|
||||||
|
private Boolean isLeader;
|
||||||
|
|
||||||
|
private List<TeaCustomGroupTreeVO> children;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.yunhai.bschclassmanage.model.vo.sync;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分班结果推送-添加班级出参(POST /oapi/class/AddClass)。
|
||||||
|
* 返回本地班号 → 平台 classId 映射,供 AddClassStudent 分组使用。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class SyncClassResultVO {
|
||||||
|
|
||||||
|
/** 起始年级编码(base_sch_system_grade.grade_id,本学段最小编码) */
|
||||||
|
private Integer gradeId;
|
||||||
|
|
||||||
|
/** 年级组id(平台组) */
|
||||||
|
private Integer gradeGroupId;
|
||||||
|
|
||||||
|
/** 班级结果(按入参顺序) */
|
||||||
|
private List<ClassItem> classes;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public static class ClassItem {
|
||||||
|
|
||||||
|
/** 班号(=入参 sort) */
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
/** 平台班级id(班级组id) */
|
||||||
|
private Integer classId;
|
||||||
|
|
||||||
|
/** 班级编码(enrollYear + 两位班号) */
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
/** 班级名称("N班") */
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** true=本次新建;false=按 code 查重命中已有班级组(幂等重推,未覆盖既有数据) */
|
||||||
|
private Boolean created;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.yunhai.bschclassmanage.model.vo.sync;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分班结果推送-添加班级学生出参(POST /oapi/class/AddClassStudent)。
|
||||||
|
* 逐生返回成败,不因个别失败整体报错(聚合模式)。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public class SyncStudentResultVO {
|
||||||
|
|
||||||
|
/** 逐生结果(与入参顺序一致) */
|
||||||
|
private List<StudentItem> results;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
public static class StudentItem {
|
||||||
|
|
||||||
|
/** 姓名 */
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 登录账号(=入参 account,可能为空) */
|
||||||
|
private String account;
|
||||||
|
|
||||||
|
/** 是否成功(含幂等命中"已存在") */
|
||||||
|
private Boolean ok;
|
||||||
|
|
||||||
|
/** 平台学生id(用户中心用户id,成功时返回) */
|
||||||
|
private Integer stuId;
|
||||||
|
|
||||||
|
/** 失败原因 / 幂等命中说明(成功时也可能非空,如"已存在") */
|
||||||
|
private String reason;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.yunhai.bschclassmanage.service;
|
package com.yunhai.bschclassmanage.service;
|
||||||
|
|
||||||
import com.yunhai.bschclassmanage.entity.BaseDicGrade;
|
import com.yunhai.bschclassmanage.entity.BaseDicGrade;
|
||||||
|
import com.yunhai.bschclassmanage.model.oa.TeaTreeVO;
|
||||||
import com.yunhai.bschclassmanage.model.vo.SchSystemTreeVO;
|
import com.yunhai.bschclassmanage.model.vo.SchSystemTreeVO;
|
||||||
import com.yunhai.bschclassmanage.model.vo.SelectIntVO;
|
import com.yunhai.bschclassmanage.model.vo.SelectIntVO;
|
||||||
import com.yunhai.bschclassmanage.model.vo.SelectStrVO;
|
import com.yunhai.bschclassmanage.model.vo.SelectStrVO;
|
||||||
@@ -28,6 +29,10 @@ public interface IComSchTreeService {
|
|||||||
|
|
||||||
BoaGroup getTeacherTree(Integer schId);
|
BoaGroup getTeacherTree(Integer schId);
|
||||||
|
|
||||||
|
List<TeaTreeVO> getSchTeaTree(Integer schId, boolean toUser);
|
||||||
|
|
||||||
|
List<TeaTreeVO> getSchStuTree(Integer schId, boolean toUser);
|
||||||
|
|
||||||
List<SelectIntVO> getSchoolList();
|
List<SelectIntVO> getSchoolList();
|
||||||
|
|
||||||
BoaGroup getTeaTree(Integer schId, Boolean withSub);
|
BoaGroup getTeaTree(Integer schId, Boolean withSub);
|
||||||
|
|||||||
@@ -22,4 +22,9 @@ public interface ISchDicSubjectService {
|
|||||||
List<SelectStrVO> select(Integer schId,Integer schsystemId);
|
List<SelectStrVO> select(Integer schId,Integer schsystemId);
|
||||||
|
|
||||||
Map<String, String> toIdAndNameMap(Integer schId, Integer schsystemId);
|
Map<String, String> toIdAndNameMap(Integer schId, Integer schsystemId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取学校学科 code -> name 映射(仅启用的学科)
|
||||||
|
*/
|
||||||
|
Map<String, String> toEnableIdAndNameMap(Integer schId, Integer schsystemId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.yunhai.bschclassmanage.service;
|
||||||
|
|
||||||
|
import com.yunhai.bschclassmanage.model.tool.SyncClassDTO;
|
||||||
|
import com.yunhai.bschclassmanage.model.tool.SyncClassStudentDTO;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.sync.SyncClassResultVO;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.sync.SyncStudentResultVO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分班结果推送开放接口(/oapi/class/)。
|
||||||
|
* 调用方为分班管理(AcaClassAssign):提交分班结果后把班级与学生落到班级管理。
|
||||||
|
*/
|
||||||
|
public interface ISyncClassService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加班级(同步组):学生组→[学段组]→年级组(name=入学年份)→班级组(code=年份+两位班号) + sch_class。
|
||||||
|
* 幂等:按组 name/code 查重复用,不覆盖既有班级数据;新建组失败时补偿删除本次新建的组。
|
||||||
|
*/
|
||||||
|
SyncClassResultVO addClass(SyncClassDTO dto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加班级学生:用户中心建号 + sch_student 落库。
|
||||||
|
* 默认聚合模式(逐生返回成败,个别失败不中断);rollbackOnError=true 走整批回滚。
|
||||||
|
* 幂等:按 account(全库)/ 班内姓名查重跳过。
|
||||||
|
*/
|
||||||
|
SyncStudentResultVO addClassStudent(SyncClassStudentDTO dto);
|
||||||
|
}
|
||||||
@@ -329,7 +329,7 @@ public class SchDicSubjectService implements ISchDicSubjectService {
|
|||||||
public List<SelectStrVO> select(Integer schId, Integer schsystemId) {
|
public List<SelectStrVO> select(Integer schId, Integer schsystemId) {
|
||||||
List<SchDicSubject> list = getSchDicSubjectCacheList(schId, schsystemId);
|
List<SchDicSubject> list = getSchDicSubjectCacheList(schId, schsystemId);
|
||||||
// 按 code 去重,保留顺序(若需要保持原顺序,可用 LinkedHashSet 包装)
|
// 按 code 去重,保留顺序(若需要保持原顺序,可用 LinkedHashSet 包装)
|
||||||
list = list.stream()
|
list = list.stream().filter(s -> Boolean.TRUE.equals(s.getEnable()))
|
||||||
.collect(Collectors.collectingAndThen(
|
.collect(Collectors.collectingAndThen(
|
||||||
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(SchDicSubject::getCode))),
|
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(SchDicSubject::getCode))),
|
||||||
ArrayList::new
|
ArrayList::new
|
||||||
@@ -342,4 +342,22 @@ public class SchDicSubjectService implements ISchDicSubjectService {
|
|||||||
List<SchDicSubject> list = getSchDicSubjectCacheList(schId,schsystemId);
|
List<SchDicSubject> list = getSchDicSubjectCacheList(schId,schsystemId);
|
||||||
return ModelMapperUtil.toMap(list, SchDicSubject::getCode, SchDicSubject::getName);
|
return ModelMapperUtil.toMap(list, SchDicSubject::getCode, SchDicSubject::getName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, String> toEnableIdAndNameMap(Integer schId, Integer schsystemId)
|
||||||
|
{
|
||||||
|
List<SchDicSubject> list = getSchDicSubjectCacheList(schId, schsystemId).stream()
|
||||||
|
.filter(s -> Boolean.TRUE.equals(s.getEnable()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
// 按序号升序排序(null 排最后),与学科管理列表口径一致
|
||||||
|
list.sort(Comparator.comparing(SchDicSubject::getSort,
|
||||||
|
Comparator.nullsLast(Comparator.naturalOrder())));
|
||||||
|
// LinkedHashMap 保持排序后的遍历顺序;重复 code 保留第一个
|
||||||
|
Map<String, String> map = new LinkedHashMap<>();
|
||||||
|
for (SchDicSubject subject : list) {
|
||||||
|
if (subject.getCode() != null) {
|
||||||
|
map.putIfAbsent(subject.getCode(), subject.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import com.yunhai.bschclassmanage.utils.LogContextHolder;
|
|||||||
import com.yunhai.bschclassmanage.utils.OtherUtil;
|
import com.yunhai.bschclassmanage.utils.OtherUtil;
|
||||||
import com.yunhai.bschclassmanage.utils.RedisUtil;
|
import com.yunhai.bschclassmanage.utils.RedisUtil;
|
||||||
import com.yunhai.bschclassmanage.utils.UserContext;
|
import com.yunhai.bschclassmanage.utils.UserContext;
|
||||||
import com.yunhai.platform.openapi.client.YschBaseOpenApi;
|
|
||||||
import com.yunhai.platform.utils.exception.DException;
|
import com.yunhai.platform.utils.exception.DException;
|
||||||
import com.yunhai.platform.utils.model.BoaGroup;
|
import com.yunhai.platform.utils.model.BoaGroup;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@@ -108,7 +107,7 @@ public class SchInfoService implements ISchInfoService {
|
|||||||
public SchInfoVO GetSchFirst(String SchID) {
|
public SchInfoVO GetSchFirst(String SchID) {
|
||||||
return schInfoRepository.findBySchSerId(SchID)
|
return schInfoRepository.findBySchSerId(SchID)
|
||||||
.map(schInfo -> SchInfoVO.builder()
|
.map(schInfo -> SchInfoVO.builder()
|
||||||
.userSchSerId(schInfo.getId())
|
.userSchSerId(schInfo.getSchId())
|
||||||
.schSerId(schInfo.getSchSerId())
|
.schSerId(schInfo.getSchSerId())
|
||||||
.schName(schInfo.getName())
|
.schName(schInfo.getName())
|
||||||
.build())
|
.build())
|
||||||
@@ -118,7 +117,7 @@ public class SchInfoService implements ISchInfoService {
|
|||||||
public SchInfoVO GetSchFirst(Integer schId) {
|
public SchInfoVO GetSchFirst(Integer schId) {
|
||||||
return schInfoRepository.findBySchId(schId)
|
return schInfoRepository.findBySchId(schId)
|
||||||
.map(schInfo -> SchInfoVO.builder()
|
.map(schInfo -> SchInfoVO.builder()
|
||||||
.userSchSerId(schInfo.getId())
|
.userSchSerId(schInfo.getSchId())
|
||||||
.schSerId(schInfo.getSchSerId())
|
.schSerId(schInfo.getSchSerId())
|
||||||
.schName(schInfo.getName())
|
.schName(schInfo.getName())
|
||||||
.build())
|
.build())
|
||||||
|
|||||||
+7
-5
@@ -300,13 +300,15 @@ public class StudentClassHistoryService implements IStudentClassHistoryService {
|
|||||||
*/
|
*/
|
||||||
public Map<String, Object> getHistoryClassTeachByStuId(Integer schId, Integer stuId) {
|
public Map<String, Object> getHistoryClassTeachByStuId(Integer schId, Integer stuId) {
|
||||||
// 1. 解析学生所属班级 & 姓名
|
// 1. 解析学生所属班级 & 姓名
|
||||||
|
// stu_id 是用户中心用户ID,学生在多所学校各有就学记录(同一 stu_id 多行),需按当前学校过滤后取一条
|
||||||
Integer classId = null;
|
Integer classId = null;
|
||||||
String studentName = null;
|
String studentName = null;
|
||||||
Optional<SchStudent> live = schStudentRepository.findByStuId(stuId);
|
SchStudent liveMatch = schStudentRepository.findByStuId(stuId).stream()
|
||||||
if (live.isPresent() && live.get().getClassId() != null
|
.filter(s -> s.getClassId() != null && Objects.equals(s.getSchId(), schId))
|
||||||
&& Objects.equals(live.get().getSchId(), schId)) {
|
.findFirst().orElse(null);
|
||||||
classId = live.get().getClassId();
|
if (liveMatch != null) {
|
||||||
studentName = live.get().getName();
|
classId = liveMatch.getClassId();
|
||||||
|
studentName = liveMatch.getName();
|
||||||
} else {
|
} else {
|
||||||
List<SchStudentHistory> hist = schStudentHistoryRepository.findBySchIdAndStuId(schId, stuId);
|
List<SchStudentHistory> hist = schStudentHistoryRepository.findBySchIdAndStuId(schId, stuId);
|
||||||
if (hist.isEmpty()) {
|
if (hist.isEmpty()) {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import com.yunhai.bschclassmanage.service.*;
|
|||||||
import com.yunhai.bschclassmanage.utils.LogContextHolder;
|
import com.yunhai.bschclassmanage.utils.LogContextHolder;
|
||||||
import com.yunhai.bschclassmanage.utils.OtherUtil;
|
import com.yunhai.bschclassmanage.utils.OtherUtil;
|
||||||
import com.yunhai.bschclassmanage.utils.UserContext;
|
import com.yunhai.bschclassmanage.utils.UserContext;
|
||||||
import com.yunhai.platform.openapi.client.YschBaseOpenApi;
|
|
||||||
import com.yunhai.platform.utils.exception.DException;
|
import com.yunhai.platform.utils.exception.DException;
|
||||||
import com.yunhai.platform.utils.model.BoaGroup;
|
import com.yunhai.platform.utils.model.BoaGroup;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ import com.yunhai.bschclassmanage.entity.*;
|
|||||||
import com.yunhai.bschclassmanage.enums.AcademicStage;
|
import com.yunhai.bschclassmanage.enums.AcademicStage;
|
||||||
import com.yunhai.bschclassmanage.enums.NodeType;
|
import com.yunhai.bschclassmanage.enums.NodeType;
|
||||||
import com.yunhai.bschclassmanage.enums.SchType;
|
import com.yunhai.bschclassmanage.enums.SchType;
|
||||||
|
import com.yunhai.bschclassmanage.model.oa.TeaTreeVO;
|
||||||
import com.yunhai.bschclassmanage.model.vo.SchSystemTreeVO;
|
import com.yunhai.bschclassmanage.model.vo.SchSystemTreeVO;
|
||||||
import com.yunhai.bschclassmanage.model.vo.SelectIntVO;
|
import com.yunhai.bschclassmanage.model.vo.SelectIntVO;
|
||||||
import com.yunhai.bschclassmanage.service.*;
|
import com.yunhai.bschclassmanage.service.*;
|
||||||
import com.yunhai.bschclassmanage.utils.RedisConstants;
|
import com.yunhai.bschclassmanage.utils.RedisConstants;
|
||||||
import com.yunhai.bschclassmanage.utils.RedisUtil;
|
import com.yunhai.bschclassmanage.utils.RedisUtil;
|
||||||
import com.yunhai.platform.openapi.client.YschBaseOpenApi;
|
import com.yunhai.platform.openapi.base.YschBaseOpenApi;
|
||||||
import com.yunhai.platform.utils.exception.DException;
|
import com.yunhai.platform.utils.exception.DException;
|
||||||
import com.yunhai.platform.utils.model.BoaGroup;
|
import com.yunhai.platform.utils.model.BoaGroup;
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
@@ -57,6 +58,10 @@ public class ComSchTreeService implements IComSchTreeService {
|
|||||||
@Lazy
|
@Lazy
|
||||||
private ISchTeacherService schTeacherService;
|
private ISchTeacherService schTeacherService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Lazy
|
||||||
|
private ISchStudentService schStudentService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SchTeacherGroupRepository schTeacherGroupRepository;
|
private SchTeacherGroupRepository schTeacherGroupRepository;
|
||||||
|
|
||||||
@@ -433,6 +438,239 @@ public class ComSchTreeService implements IComSchTreeService {
|
|||||||
return teaTree;
|
return teaTree;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 教师叶子的"未分组"节点 ID(与 getTeacherTree 的默认组保持一致) */
|
||||||
|
private static final int UNGROUPED_ID = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 教师组树 + 教师(SchGroup/GetTeaTree 输出)。
|
||||||
|
* 结构:学校根(type=1) → 教师组(type=1) → 子组递归 + 教师叶子(type=2)。
|
||||||
|
* 节点字段:pid=父节点id(根为0)、no=组取 BoaGroup.sort / 教师固定 0、
|
||||||
|
* isParent=组true/教师false、photo 固定空串(本库无头像数据)。
|
||||||
|
* toUser=false 时只返回组织骨架(不查询、不挂载教师叶子)。
|
||||||
|
* 返回单元素列表(学校根节点)。
|
||||||
|
*/
|
||||||
|
public List<TeaTreeVO> getSchTeaTree(Integer schId, boolean toUser) {
|
||||||
|
// 1. 学校根节点(远程组树的根即学校节点,取不到时用学校名兜底)
|
||||||
|
List<BoaGroup> schTree = getSchTree(schId);
|
||||||
|
BoaGroup schRoot = (schTree == null || schTree.isEmpty()) ? null : schTree.get(0);
|
||||||
|
TeaTreeVO rootVo = new TeaTreeVO();
|
||||||
|
if (schRoot != null && schRoot.getId() != null) {
|
||||||
|
rootVo.setId(schRoot.getId());
|
||||||
|
rootVo.setName(schRoot.getName());
|
||||||
|
rootVo.setNo(schRoot.getSort());
|
||||||
|
} else {
|
||||||
|
rootVo.setId(schId);
|
||||||
|
rootVo.setName(getSchoolName(schId));
|
||||||
|
rootVo.setNo(0);
|
||||||
|
}
|
||||||
|
rootVo.setPid(0);
|
||||||
|
rootVo.setType(1);
|
||||||
|
rootVo.setIsParent(true);
|
||||||
|
rootVo.setChildren(new ArrayList<>());
|
||||||
|
|
||||||
|
// 2. 教师组骨架(type=7 子树)转 VO,同时建立 groupId -> 节点 映射供挂教师用
|
||||||
|
Map<Integer, TeaTreeVO> groupVoMap = new HashMap<>();
|
||||||
|
BoaGroup teaTree = getTeaTree(schId, true);
|
||||||
|
TeaTreeVO teaRootVo;
|
||||||
|
if (teaTree == null || teaTree.getId() == null) {
|
||||||
|
teaRootVo = newGroupNode(0, "教师组", rootVo.getId(), 1);
|
||||||
|
} else {
|
||||||
|
teaRootVo = convertGroupTree(teaTree, rootVo.getId(), groupVoMap);
|
||||||
|
}
|
||||||
|
groupVoMap.put(teaRootVo.getId(), teaRootVo);
|
||||||
|
rootVo.getChildren().add(teaRootVo);
|
||||||
|
|
||||||
|
// 3. 挂教师:多组各挂一份;无组或组不在树中的落到"未分组"(toUser=false 时不带用户,跳过查询)
|
||||||
|
if (toUser) {
|
||||||
|
List<SchTeacher> teacherList = schTeacherService.getSchTeacherList(schId);
|
||||||
|
if (CollectionUtils.isNotEmpty(teacherList)) {
|
||||||
|
List<Integer> teaIds = teacherList.stream()
|
||||||
|
.map(SchTeacher::getTeaId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
Map<Integer, List<Integer>> teaIdToGroupIds = schTeacherGroupRepository.findByTeaIdInAndSchId(teaIds, schId)
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.groupingBy(SchTeacherGroup::getTeaId,
|
||||||
|
Collectors.mapping(SchTeacherGroup::getGroupId, Collectors.toList())));
|
||||||
|
|
||||||
|
for (SchTeacher teacher : teacherList) {
|
||||||
|
if (teacher.getTeaId() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List<Integer> groupIds = teaIdToGroupIds.getOrDefault(teacher.getTeaId(), Collections.emptyList());
|
||||||
|
if (groupIds.isEmpty()) {
|
||||||
|
TeaTreeVO ungrouped = getOrCreateUngrouped(teaRootVo, groupVoMap);
|
||||||
|
ungrouped.getChildren().add(newTeacherNode(teacher, ungrouped.getId()));
|
||||||
|
} else {
|
||||||
|
for (Integer groupId : groupIds) {
|
||||||
|
TeaTreeVO groupVo = groupVoMap.get(groupId);
|
||||||
|
if (groupVo == null) {
|
||||||
|
groupVo = getOrCreateUngrouped(teaRootVo, groupVoMap);
|
||||||
|
}
|
||||||
|
// 同一节点下按 teaId 去重(防脏数据中同一组出现多条关联)
|
||||||
|
boolean exists = groupVo.getChildren().stream()
|
||||||
|
.anyMatch(c -> Integer.valueOf(2).equals(c.getType()) && c.getId().equals(teacher.getTeaId()));
|
||||||
|
if (!exists) {
|
||||||
|
groupVo.getChildren().add(newTeacherNode(teacher, groupVo.getId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 每个节点 children 按 no 升序(nullsLast、稳定排序):教师 no=0 排在子组之前,
|
||||||
|
// 同 no 的教师保持 getSchTeacherList 的 sort 顺序
|
||||||
|
sortChildrenByNo(rootVo);
|
||||||
|
|
||||||
|
return Collections.singletonList(rootVo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 组节点 VO(type=1,isParent=true,children 初始化为空列表) */
|
||||||
|
private TeaTreeVO newGroupNode(Integer id, String name, Integer pid, Integer no) {
|
||||||
|
TeaTreeVO vo = new TeaTreeVO();
|
||||||
|
vo.setId(id);
|
||||||
|
vo.setName(name);
|
||||||
|
vo.setPid(pid);
|
||||||
|
vo.setNo(no);
|
||||||
|
vo.setType(1);
|
||||||
|
vo.setIsParent(true);
|
||||||
|
vo.setChildren(new ArrayList<>());
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 教师叶子 VO(type=2,isParent=false,no 固定 0,children=null → JSON 省略) */
|
||||||
|
private TeaTreeVO newTeacherNode(SchTeacher teacher, Integer pid) {
|
||||||
|
TeaTreeVO vo = new TeaTreeVO();
|
||||||
|
vo.setId(teacher.getTeaId());
|
||||||
|
vo.setName(teacher.getName());
|
||||||
|
vo.setPid(pid);
|
||||||
|
vo.setNo(0);
|
||||||
|
vo.setType(2);
|
||||||
|
vo.setIsParent(false);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 递归把 BoaGroup 骨架转成 TeaTreeVO(type=1),pid 记录父节点 id,并收集 groupId -> 节点 */
|
||||||
|
private TeaTreeVO convertGroupTree(BoaGroup node, Integer pid, Map<Integer, TeaTreeVO> groupVoMap) {
|
||||||
|
TeaTreeVO vo = newGroupNode(node.getId(), node.getName(), pid, node.getSort());
|
||||||
|
groupVoMap.put(node.getId(), vo);
|
||||||
|
if (node.getChildren() != null) {
|
||||||
|
for (BoaGroup child : node.getChildren()) {
|
||||||
|
vo.getChildren().add(convertGroupTree(child, node.getId(), groupVoMap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取或惰性创建"未分组"节点(挂在教师组根下) */
|
||||||
|
private TeaTreeVO getOrCreateUngrouped(TeaTreeVO teaRootVo, Map<Integer, TeaTreeVO> groupVoMap) {
|
||||||
|
TeaTreeVO ungrouped = groupVoMap.get(UNGROUPED_ID);
|
||||||
|
if (ungrouped == null) {
|
||||||
|
ungrouped = newGroupNode(UNGROUPED_ID, "未分组", teaRootVo.getId(), null);
|
||||||
|
teaRootVo.getChildren().add(ungrouped);
|
||||||
|
groupVoMap.put(UNGROUPED_ID, ungrouped);
|
||||||
|
}
|
||||||
|
return ungrouped;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 递归按 no 升序排序每个节点的 children(nullsLast、稳定排序) */
|
||||||
|
private void sortChildrenByNo(TeaTreeVO node) {
|
||||||
|
if (node == null || node.getChildren() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
node.getChildren().sort(Comparator.comparing(TeaTreeVO::getNo, Comparator.nullsLast(Integer::compareTo)));
|
||||||
|
for (TeaTreeVO child : node.getChildren()) {
|
||||||
|
sortChildrenByNo(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学生树(SchGroup/GetStuTree 输出):学校 → 学段 → 年级 → 班级 → 学生。
|
||||||
|
* 层级数据链与 getGradeClassTree 一致(学段=base_sch_system、年级=base_sch_system_grade(no=主键 id)、
|
||||||
|
* 班级=sch_class(id=group_class_id,名称 cname 优先,no=sort,不含毕业班),
|
||||||
|
* 学生仅取在读正常状态(复用按班缓存查询,已过滤休学/转出/禁用并按 sort 排序)。
|
||||||
|
* 节点契约与 getSchTeaTree 相同:组织节点 type=1、isParent=true;学生叶子 type=2、isParent=false、children=null。
|
||||||
|
* toUser=false 时只返回组织骨架(不查询、不挂载学生叶子)。
|
||||||
|
* 返回单元素列表(学校根节点)。
|
||||||
|
*/
|
||||||
|
public List<TeaTreeVO> getSchStuTree(Integer schId, boolean toUser) {
|
||||||
|
TeaTreeVO rootVo = new TeaTreeVO();
|
||||||
|
rootVo.setId(schId);
|
||||||
|
rootVo.setName(getSchoolName(schId));
|
||||||
|
rootVo.setPid(0);
|
||||||
|
rootVo.setNo(0);
|
||||||
|
rootVo.setType(1);
|
||||||
|
rootVo.setIsParent(true);
|
||||||
|
rootVo.setChildren(new ArrayList<>());
|
||||||
|
|
||||||
|
List<SelectIntVO> systemList = schBinSchsystemService.selectSchBinSchsystem(schId);
|
||||||
|
if (CollectionUtils.isEmpty(systemList)) {
|
||||||
|
return Collections.singletonList(rootVo);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (SelectIntVO system : systemList) {
|
||||||
|
TeaTreeVO systemVo = newGroupNode(system.getId(), system.getName(), schId, null);
|
||||||
|
rootVo.getChildren().add(systemVo);
|
||||||
|
|
||||||
|
List<BaseSchsystemGrade> gradeList = baseSchsystemGradeService.getSchGradeCacheList(schId, system.getId(), null);
|
||||||
|
if (CollectionUtils.isEmpty(gradeList)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List<Integer> gradeIds = gradeList.stream()
|
||||||
|
.map(BaseSchsystemGrade::getGradeId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
Map<Integer, List<SchClass>> classGroupMap = schClassService.getSchClassCacheFilter(schId, gradeIds, null)
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.groupingBy(SchClass::getGradeId));
|
||||||
|
|
||||||
|
for (BaseSchsystemGrade grade : gradeList) {
|
||||||
|
if (grade.getGradeId() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
TeaTreeVO gradeVo = newGroupNode(grade.getGradeId(), grade.getGradeNickname(), system.getId(), grade.getId());
|
||||||
|
systemVo.getChildren().add(gradeVo);
|
||||||
|
|
||||||
|
for (SchClass clazz : classGroupMap.getOrDefault(grade.getGradeId(), Collections.emptyList())) {
|
||||||
|
if (clazz.getClassId() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
TeaTreeVO classVo = newGroupNode(clazz.getClassId(),
|
||||||
|
StringUtils.isBlank(clazz.getCname()) ? clazz.getName() : clazz.getCname(),
|
||||||
|
grade.getGradeId(), clazz.getSort());
|
||||||
|
gradeVo.getChildren().add(classVo);
|
||||||
|
|
||||||
|
// toUser=false 时不带用户,跳过按班查询
|
||||||
|
if (toUser) {
|
||||||
|
List<SchStudent> students = schStudentService.getSchGradeClassStudentCacheList(schId, null, null, clazz.getClassId());
|
||||||
|
for (SchStudent student : students) {
|
||||||
|
if (student.getStuId() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
classVo.getChildren().add(newStudentNode(student, clazz.getClassId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sortChildrenByNo(rootVo);
|
||||||
|
return Collections.singletonList(rootVo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 学生叶子 VO(type=2,isParent=false,no 固定 0,children=null → JSON 省略) */
|
||||||
|
private TeaTreeVO newStudentNode(SchStudent student, Integer pid) {
|
||||||
|
TeaTreeVO vo = new TeaTreeVO();
|
||||||
|
vo.setId(student.getStuId());
|
||||||
|
vo.setName(student.getName());
|
||||||
|
vo.setPid(pid);
|
||||||
|
vo.setNo(0);
|
||||||
|
vo.setType(2);
|
||||||
|
vo.setIsParent(false);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 递归确保所有节点的 children 不为 null,若为 null 则设置为空列表 []
|
* 递归确保所有节点的 children 不为 null,若为 null 则设置为空列表 []
|
||||||
*/
|
*/
|
||||||
|
|||||||
+10
-10
@@ -12,10 +12,10 @@ import com.yunhai.bschclassmanage.model.dto.SchTeacherDTO;
|
|||||||
import com.yunhai.bschclassmanage.model.dto.SendMoveGroupUserDTO;
|
import com.yunhai.bschclassmanage.model.dto.SendMoveGroupUserDTO;
|
||||||
import com.yunhai.bschclassmanage.service.IComSchTreeService;
|
import com.yunhai.bschclassmanage.service.IComSchTreeService;
|
||||||
import com.yunhai.bschclassmanage.service.ISendUserBaseService;
|
import com.yunhai.bschclassmanage.service.ISendUserBaseService;
|
||||||
import com.yunhai.platform.openapi.client.OpenApiGroupClient;
|
import com.yunhai.platform.openapi.base.OpenApiGroupClient;
|
||||||
import com.yunhai.platform.openapi.client.OpenApiUserClient;
|
import com.yunhai.platform.openapi.base.OpenApiUserClient;
|
||||||
import com.yunhai.platform.openapi.client.YschBaseOpenApi;
|
import com.yunhai.platform.openapi.base.YschBaseOpenApi;
|
||||||
import com.yunhai.platform.openapi.model.*;
|
import com.yunhai.platform.openapi.base.model.*;
|
||||||
import com.yunhai.platform.utils.exception.DException;
|
import com.yunhai.platform.utils.exception.DException;
|
||||||
import com.yunhai.platform.utils.model.BoaGroup;
|
import com.yunhai.platform.utils.model.BoaGroup;
|
||||||
import com.yunhai.platform.utils.model.BoaUser;
|
import com.yunhai.platform.utils.model.BoaUser;
|
||||||
@@ -103,7 +103,7 @@ public class SendUserBaseService implements ISendUserBaseService {
|
|||||||
sysUser.setIdcard(teacher.getIdCard());
|
sysUser.setIdcard(teacher.getIdCard());
|
||||||
sysUser.setPhone(teacher.getTel());
|
sysUser.setPhone(teacher.getTel());
|
||||||
sysUser.setGroupId(groupId);
|
sysUser.setGroupId(groupId);
|
||||||
sysUser.setRoles(Arrays.asList(UserRole.TEACHER_USER.getValue()));
|
sysUser.setRoleIds(Arrays.asList(UserRole.TEACHER_USER.getValue()));
|
||||||
if (StringUtils.isNotBlank(pwd)) {
|
if (StringUtils.isNotBlank(pwd)) {
|
||||||
sysUser.setPassword(pwd);
|
sysUser.setPassword(pwd);
|
||||||
sysUser.setConfirmPassword(pwd);
|
sysUser.setConfirmPassword(pwd);
|
||||||
@@ -119,7 +119,7 @@ public class SendUserBaseService implements ISendUserBaseService {
|
|||||||
sysUser.setGender(student.getGender());
|
sysUser.setGender(student.getGender());
|
||||||
sysUser.setIdcard(student.getIdCard());
|
sysUser.setIdcard(student.getIdCard());
|
||||||
sysUser.setGroupId(student.getGroupId());
|
sysUser.setGroupId(student.getGroupId());
|
||||||
sysUser.setRoles(Arrays.asList(UserRole.STU_USER.getValue()));
|
sysUser.setRoleIds(Arrays.asList(UserRole.STU_USER.getValue()));
|
||||||
if (StringUtils.isNotBlank(pwd)) {
|
if (StringUtils.isNotBlank(pwd)) {
|
||||||
sysUser.setPassword(pwd);
|
sysUser.setPassword(pwd);
|
||||||
sysUser.setConfirmPassword(pwd);
|
sysUser.setConfirmPassword(pwd);
|
||||||
@@ -139,7 +139,7 @@ public class SendUserBaseService implements ISendUserBaseService {
|
|||||||
user.setIdcard(teacher.getIdCard());
|
user.setIdcard(teacher.getIdCard());
|
||||||
user.setPhone(teacher.getTel());
|
user.setPhone(teacher.getTel());
|
||||||
user.setGroupId(groupId);
|
user.setGroupId(groupId);
|
||||||
user.setRoles(Arrays.asList(UserRole.TEACHER_USER.getValue()));
|
user.setRoleIds(Arrays.asList(UserRole.TEACHER_USER.getValue()));
|
||||||
if (StringUtils.isNotBlank(pwd)) {
|
if (StringUtils.isNotBlank(pwd)) {
|
||||||
user.setPassword(pwd);
|
user.setPassword(pwd);
|
||||||
}
|
}
|
||||||
@@ -157,7 +157,7 @@ public class SendUserBaseService implements ISendUserBaseService {
|
|||||||
user.setGender(student.getGender());
|
user.setGender(student.getGender());
|
||||||
user.setIdcard(student.getIdCard());
|
user.setIdcard(student.getIdCard());
|
||||||
user.setGroupId(student.getGroupId());
|
user.setGroupId(student.getGroupId());
|
||||||
user.setRoles(Arrays.asList(UserRole.STU_USER.getValue()));
|
user.setRoleIds(Arrays.asList(UserRole.STU_USER.getValue()));
|
||||||
if (StringUtils.isNotBlank(pwd)) {
|
if (StringUtils.isNotBlank(pwd)) {
|
||||||
user.setPassword(pwd);
|
user.setPassword(pwd);
|
||||||
}
|
}
|
||||||
@@ -298,7 +298,7 @@ public class SendUserBaseService implements ISendUserBaseService {
|
|||||||
sysUser.setGender(stu.getGender());
|
sysUser.setGender(stu.getGender());
|
||||||
sysUser.setIdcard(stu.getIdCard());
|
sysUser.setIdcard(stu.getIdCard());
|
||||||
sysUser.setGroupId(stu.getGroupId());
|
sysUser.setGroupId(stu.getGroupId());
|
||||||
sysUser.setRoles(Arrays.asList(UserRole.STU_USER.getValue()));
|
sysUser.setRoleIds(Arrays.asList(UserRole.STU_USER.getValue()));
|
||||||
String pwd = stu.getPwd();
|
String pwd = stu.getPwd();
|
||||||
if (StringUtils.isNotBlank(pwd)) {
|
if (StringUtils.isNotBlank(pwd)) {
|
||||||
sysUser.setPassword(pwd);
|
sysUser.setPassword(pwd);
|
||||||
@@ -324,7 +324,7 @@ public class SendUserBaseService implements ISendUserBaseService {
|
|||||||
sysUser.setIdcard(teacher.getIdCard());
|
sysUser.setIdcard(teacher.getIdCard());
|
||||||
sysUser.setPhone(teacher.getTel());
|
sysUser.setPhone(teacher.getTel());
|
||||||
sysUser.setGroupId(teacher.getGroupId());
|
sysUser.setGroupId(teacher.getGroupId());
|
||||||
sysUser.setRoles(Arrays.asList(UserRole.TEACHER_USER.getValue()));
|
sysUser.setRoleIds(Arrays.asList(UserRole.TEACHER_USER.getValue()));
|
||||||
String pwd = teacher.getPwd();
|
String pwd = teacher.getPwd();
|
||||||
if (StringUtils.isNotBlank(pwd)) {
|
if (StringUtils.isNotBlank(pwd)) {
|
||||||
sysUser.setPassword(pwd);
|
sysUser.setPassword(pwd);
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import com.yunhai.bschclassmanage.model.vo.SelectIntVO;
|
|||||||
import com.yunhai.bschclassmanage.service.*;
|
import com.yunhai.bschclassmanage.service.*;
|
||||||
import com.yunhai.bschclassmanage.utils.RedisConstants;
|
import com.yunhai.bschclassmanage.utils.RedisConstants;
|
||||||
import com.yunhai.bschclassmanage.utils.RedisUtil;
|
import com.yunhai.bschclassmanage.utils.RedisUtil;
|
||||||
import com.yunhai.platform.openapi.client.YschBaseOpenApi;
|
|
||||||
import com.yunhai.platform.utils.exception.DException;
|
import com.yunhai.platform.utils.exception.DException;
|
||||||
import com.yunhai.platform.utils.model.BoaGroup;
|
import com.yunhai.platform.utils.model.BoaGroup;
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
|||||||
-1
@@ -21,7 +21,6 @@ import com.yunhai.bschclassmanage.utils.EasyPoiUtils;
|
|||||||
import com.yunhai.bschclassmanage.utils.UserContext;
|
import com.yunhai.bschclassmanage.utils.UserContext;
|
||||||
import com.yunhai.bschclassmanage.utils.ValidateUtil;
|
import com.yunhai.bschclassmanage.utils.ValidateUtil;
|
||||||
import com.yunhai.bschclassmanage.utils.ValidationUtil;
|
import com.yunhai.bschclassmanage.utils.ValidationUtil;
|
||||||
import com.yunhai.platform.openapi.client.YschBaseOpenApi;
|
|
||||||
import com.yunhai.platform.utils.model.BoaUser;
|
import com.yunhai.platform.utils.model.BoaUser;
|
||||||
import com.yunhai.platform.utils.exception.DException;
|
import com.yunhai.platform.utils.exception.DException;
|
||||||
import com.yunhai.platform.utils.exception.ErrorCode;
|
import com.yunhai.platform.utils.exception.ErrorCode;
|
||||||
|
|||||||
@@ -0,0 +1,437 @@
|
|||||||
|
package com.yunhai.bschclassmanage.service.impl.sync;
|
||||||
|
|
||||||
|
import com.yunhai.bschclassmanage.Repository.SchClassRepository;
|
||||||
|
import com.yunhai.bschclassmanage.Repository.SchStudentRepository;
|
||||||
|
import com.yunhai.bschclassmanage.entity.BaseSchsystemGrade;
|
||||||
|
import com.yunhai.bschclassmanage.entity.SchClass;
|
||||||
|
import com.yunhai.bschclassmanage.entity.SchStudent;
|
||||||
|
import com.yunhai.bschclassmanage.enums.AcademicStage;
|
||||||
|
import com.yunhai.bschclassmanage.enums.NodeType;
|
||||||
|
import com.yunhai.bschclassmanage.enums.StuAbnormalStatus;
|
||||||
|
import com.yunhai.bschclassmanage.enums.TransferStatus;
|
||||||
|
import com.yunhai.bschclassmanage.enums.UserStatus;
|
||||||
|
import com.yunhai.bschclassmanage.model.tool.SyncClassDTO;
|
||||||
|
import com.yunhai.bschclassmanage.model.tool.SyncClassStudentDTO;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.SelectIntVO;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.sync.SyncClassResultVO;
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.sync.SyncStudentResultVO;
|
||||||
|
import com.yunhai.bschclassmanage.service.IBaseSchsystemGradeService;
|
||||||
|
import com.yunhai.bschclassmanage.service.IBaseSchsystemService;
|
||||||
|
import com.yunhai.bschclassmanage.service.IComSchTreeService;
|
||||||
|
import com.yunhai.bschclassmanage.service.ISchBinSchsystemService;
|
||||||
|
import com.yunhai.bschclassmanage.service.ISendUserBaseService;
|
||||||
|
import com.yunhai.bschclassmanage.service.ISyncClassService;
|
||||||
|
import com.yunhai.bschclassmanage.service.impl.classes.SchClassCacheService;
|
||||||
|
import com.yunhai.bschclassmanage.service.impl.student.SchStudentCacheService;
|
||||||
|
import com.yunhai.platform.utils.exception.DException;
|
||||||
|
import com.yunhai.platform.utils.model.BoaGroup;
|
||||||
|
import com.yunhai.platform.utils.model.BoaUser;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分班结果推送开放接口实现。
|
||||||
|
* <p>组创建模板:SchClassInfoService.addBaseSchClass;差异①:入学年份取入参 enrollYear(不用 NowYearTerm 推导,
|
||||||
|
* 分班多发生在 6-8 月旧学年末,照抄模板会把年级组建到旧年份);差异②:班号以入参 sort 为准(分班结果已定班号,
|
||||||
|
* 不按 findMaxCodeNumber 自动续号)。</p>
|
||||||
|
* <p>回滚策略:本地事务 + 外部系统补偿删除(rollbackCreatedGroups/rollbackSyncedStudents)+ 幂等重推自愈;
|
||||||
|
* createdGroupIds 只记本次新建的组,查重复用的既有组绝不进回滚清单。</p>
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class SyncClassService implements ISyncClassService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SchClassRepository schClassRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SchStudentRepository schStudentRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IComSchTreeService comSchTreeService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ISchBinSchsystemService schBinSchsystemService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IBaseSchsystemService baseSchsystemService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IBaseSchsystemGradeService baseSchsystemGradeService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ISendUserBaseService sendUserBaseService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SchClassCacheService classCacheService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SchStudentCacheService studentCacheService;
|
||||||
|
|
||||||
|
// ==================== 添加班级(同步组) ====================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public SyncClassResultVO addClass(SyncClassDTO dto) {
|
||||||
|
if (dto == null || dto.getSchId() == null || dto.getSchId() <= 0) {
|
||||||
|
throw new DException("schId不能为空");
|
||||||
|
}
|
||||||
|
String stageName = AcademicStage.nameById(dto.getStage() == null ? -1 : dto.getStage());
|
||||||
|
if (stageName == null) {
|
||||||
|
throw new DException("stage参数非法(AcademicStage口径:1小学/2初中/3高中)");
|
||||||
|
}
|
||||||
|
if (dto.getEnrollYear() == null || dto.getEnrollYear() <= 0) {
|
||||||
|
throw new DException("enrollYear不能为空(入学年份需显式传入)");
|
||||||
|
}
|
||||||
|
if (dto.getClasses() == null || dto.getClasses().isEmpty()) {
|
||||||
|
throw new DException("classes不能为空");
|
||||||
|
}
|
||||||
|
Set<Integer> sorts = new HashSet<>();
|
||||||
|
for (SyncClassDTO.ClassItem item : dto.getClasses()) {
|
||||||
|
if (item.getSort() == null || item.getSort() <= 0 || item.getSort() > 99) {
|
||||||
|
throw new DException("班级班号非法(须为1-99):" + item.getSort());
|
||||||
|
}
|
||||||
|
if (!sorts.add(item.getSort())) {
|
||||||
|
throw new DException("班级班号重复:" + item.getSort());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Integer schId = dto.getSchId();
|
||||||
|
Integer enrollYear = dto.getEnrollYear();
|
||||||
|
|
||||||
|
// 1. 学生组(组树在用户中心,不在本地库)
|
||||||
|
BoaGroup stuGroup = comSchTreeService.getStuTree(schId, false);
|
||||||
|
if (stuGroup == null) {
|
||||||
|
throw new DException("学生组不存在!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 学段定位:逐学制比对学段名(起始年级编码由各校 base_sch_system_grade 数据决定,不硬编码)
|
||||||
|
List<Integer> schsystemIds = schBinSchsystemService.getSchBinSchsystemIds(schId);
|
||||||
|
Integer schsystemId = null;
|
||||||
|
for (Integer id : schsystemIds) {
|
||||||
|
if (stageName.equals(baseSchsystemService.getAcademicStageName(id))) {
|
||||||
|
schsystemId = id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (schsystemId == null) {
|
||||||
|
throw new DException("学校未配置该学段:" + stageName);
|
||||||
|
}
|
||||||
|
List<BaseSchsystemGrade> gradeList = baseSchsystemGradeService.getSchGradeCacheList(schId, schsystemId, null);
|
||||||
|
if (gradeList == null || gradeList.isEmpty()) {
|
||||||
|
throw new DException("学校学制未配置年级,无法定位起始年级");
|
||||||
|
}
|
||||||
|
gradeList.sort(Comparator.comparing(BaseSchsystemGrade::getGradeId));
|
||||||
|
Integer gradeId = gradeList.get(0).getGradeId();
|
||||||
|
|
||||||
|
List<Integer> createdGroupIds = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
Integer parentId = stuGroup.getId();
|
||||||
|
|
||||||
|
// 3. 多学段学校:查/建学段组
|
||||||
|
if (schsystemIds.size() > 1) {
|
||||||
|
List<BoaGroup> stageGroups = comSchTreeService.getAcademicStageGroupList(schId);
|
||||||
|
Optional<BoaGroup> hit = stageGroups == null ? Optional.empty()
|
||||||
|
: stageGroups.stream().filter(g -> stageName.equals(g.getName())).findFirst();
|
||||||
|
if (hit.isPresent()) {
|
||||||
|
parentId = hit.get().getId();
|
||||||
|
} else {
|
||||||
|
BoaGroup sectionGroup = sendUserBaseService.addGroup(stageName, parentId, schsystemId, NodeType.SECTION, schId);
|
||||||
|
createdGroupIds.add(sectionGroup.getId());
|
||||||
|
parentId = sectionGroup.getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 查/建年级组(⚠️ 多学段学校各学段都有同年份同名年级组,必须 .get(schsystemId) 过滤本学段)
|
||||||
|
Integer gradeGroupId = null;
|
||||||
|
List<SelectIntVO> gradeGroups = comSchTreeService.getSchGroupTypeList(schId, NodeType.GRADE.getId()).get(schsystemId);
|
||||||
|
if (gradeGroups != null) {
|
||||||
|
Optional<SelectIntVO> hit = gradeGroups.stream()
|
||||||
|
.filter(g -> enrollYear.toString().equals(g.getName())).findFirst();
|
||||||
|
if (hit.isPresent()) {
|
||||||
|
gradeGroupId = hit.get().getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (gradeGroupId == null) {
|
||||||
|
BoaGroup gradeGroup = sendUserBaseService.addGroup(enrollYear.toString(), parentId, enrollYear, NodeType.GRADE, schId);
|
||||||
|
createdGroupIds.add(gradeGroup.getId());
|
||||||
|
gradeGroupId = gradeGroup.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 逐班查/建班级组(code=入学年份+两位班号,入参 sort 为准)+ sch_class 幂等落库
|
||||||
|
List<SelectIntVO> classGroups = comSchTreeService.getSchGroupTypeList(schId, NodeType.CLASS.getId()).get(schsystemId);
|
||||||
|
Map<String, Integer> classGroupByName = classGroups == null ? Collections.emptyMap()
|
||||||
|
: classGroups.stream().collect(Collectors.toMap(SelectIntVO::getName, SelectIntVO::getId, (a, b) -> a));
|
||||||
|
|
||||||
|
Date now = new Date();
|
||||||
|
List<SchClass> saveList = new ArrayList<>();
|
||||||
|
List<SyncClassResultVO.ClassItem> outClasses = new ArrayList<>();
|
||||||
|
for (SyncClassDTO.ClassItem item : dto.getClasses()) {
|
||||||
|
int sort = item.getSort();
|
||||||
|
String code = enrollYear + String.format("%02d", sort);
|
||||||
|
Integer classGroupId = classGroupByName.get(code);
|
||||||
|
boolean created = false;
|
||||||
|
if (classGroupId == null) {
|
||||||
|
BoaGroup classGroup = sendUserBaseService.addGroup(code, gradeGroupId, sort, NodeType.CLASS, schId);
|
||||||
|
createdGroupIds.add(classGroup.getId());
|
||||||
|
classGroupId = classGroup.getId();
|
||||||
|
created = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sch_class 按 classId(班级组id)幂等:已有行不覆盖(要修改走班级管理编辑功能);缺行补建
|
||||||
|
SchClass existRow = schClassRepository.findByClassIdAndSchId(classGroupId, schId).orElse(null);
|
||||||
|
String outName = sort + "班";
|
||||||
|
if (existRow == null) {
|
||||||
|
SchClass row = new SchClass();
|
||||||
|
row.setSchId(schId);
|
||||||
|
row.setGradeId(gradeId);
|
||||||
|
row.setSort(sort);
|
||||||
|
row.setCode(code);
|
||||||
|
row.setName(outName);
|
||||||
|
row.setCname(item.getName());
|
||||||
|
row.setGroupId(gradeGroupId);
|
||||||
|
row.setClassId(classGroupId);
|
||||||
|
row.setGraduate(false);
|
||||||
|
row.setEnrollYear(enrollYear);
|
||||||
|
if (item.getTeacherId() != null) {
|
||||||
|
row.setFirstTeacherId(item.getTeacherId());
|
||||||
|
}
|
||||||
|
row.setCreateDate(now);
|
||||||
|
row.setUpdateDate(now);
|
||||||
|
saveList.add(row);
|
||||||
|
} else {
|
||||||
|
outName = existRow.getName() != null ? existRow.getName() : outName;
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncClassResultVO.ClassItem out = new SyncClassResultVO.ClassItem();
|
||||||
|
out.setSort(sort);
|
||||||
|
out.setClassId(classGroupId);
|
||||||
|
out.setCode(code);
|
||||||
|
out.setName(outName);
|
||||||
|
out.setCreated(created);
|
||||||
|
outClasses.add(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!saveList.isEmpty()) {
|
||||||
|
schClassRepository.saveAll(saveList);
|
||||||
|
}
|
||||||
|
classCacheService.clearClassCache(schId);
|
||||||
|
|
||||||
|
SyncClassResultVO vo = new SyncClassResultVO();
|
||||||
|
vo.setGradeId(gradeId);
|
||||||
|
vo.setGradeGroupId(gradeGroupId);
|
||||||
|
vo.setClasses(outClasses);
|
||||||
|
log.info("[addClass] schId={}, stage={}, enrollYear={}, 新建组={}, 班级 {} 个(新建 {})",
|
||||||
|
schId, stageName, enrollYear, createdGroupIds.size(), outClasses.size(),
|
||||||
|
outClasses.stream().filter(c -> Boolean.TRUE.equals(c.getCreated())).count());
|
||||||
|
return vo;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 组的删除是对用户中心的远程调用,不归本地事务管,须显式补偿;清单只含本次新建的组
|
||||||
|
log.error("[addClass] 添加班级失败,回滚本次新建组 {} 个:{}", createdGroupIds.size(), createdGroupIds, e);
|
||||||
|
sendUserBaseService.rollbackCreatedGroups(createdGroupIds, schId);
|
||||||
|
if (e instanceof DException) {
|
||||||
|
throw (DException) e;
|
||||||
|
}
|
||||||
|
throw new DException("添加班级失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 添加班级学生(同步学生) ====================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public SyncStudentResultVO addClassStudent(SyncClassStudentDTO dto) {
|
||||||
|
if (dto == null || dto.getSchId() == null || dto.getSchId() <= 0) {
|
||||||
|
throw new DException("schId不能为空");
|
||||||
|
}
|
||||||
|
if (dto.getStudents() == null || dto.getStudents().isEmpty()) {
|
||||||
|
throw new DException("students不能为空");
|
||||||
|
}
|
||||||
|
Integer schId = dto.getSchId();
|
||||||
|
boolean allOrNothing = Boolean.TRUE.equals(dto.getRollbackOnError());
|
||||||
|
|
||||||
|
// 整批预检:classId 必须属于本校(此时无任何副作用,直接报错)
|
||||||
|
Set<Integer> classIdSet = new LinkedHashSet<>();
|
||||||
|
for (SyncClassStudentDTO.StudentItem stu : dto.getStudents()) {
|
||||||
|
if (stu.getClassId() != null) {
|
||||||
|
classIdSet.add(stu.getClassId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (classIdSet.isEmpty()) {
|
||||||
|
throw new DException("students[].classId不能为空");
|
||||||
|
}
|
||||||
|
List<SchClass> belongClasses = schClassRepository.findBySchIdAndClassIds(schId, new ArrayList<>(classIdSet));
|
||||||
|
Map<Integer, SchClass> classMap = belongClasses.stream()
|
||||||
|
.collect(Collectors.toMap(SchClass::getClassId, c -> c, (a, b) -> a));
|
||||||
|
for (Integer classId : classIdSet) {
|
||||||
|
if (!classMap.containsKey(classId)) {
|
||||||
|
throw new DException("班级不存在或不属于本校:classId=" + classId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Date now = new Date();
|
||||||
|
List<SchStudent> saveList = new ArrayList<>();
|
||||||
|
// 已建平台账号(按下标与 groupIds 对应,供整批回滚)
|
||||||
|
List<BoaUser> syncedList = new ArrayList<>();
|
||||||
|
List<Integer> syncedGroupIds = new ArrayList<>();
|
||||||
|
// 本批内 (classId, name) / account 查重(库内查重看不到本批未落库的行)
|
||||||
|
Set<String> batchNameKeys = new HashSet<>();
|
||||||
|
Set<String> batchAccounts = new HashSet<>();
|
||||||
|
List<SyncStudentResultVO.StudentItem> results = new ArrayList<>();
|
||||||
|
|
||||||
|
for (SyncClassStudentDTO.StudentItem stu : dto.getStudents()) {
|
||||||
|
SyncStudentResultVO.StudentItem r = new SyncStudentResultVO.StudentItem();
|
||||||
|
r.setName(stu.getName());
|
||||||
|
r.setAccount(stu.getAccount());
|
||||||
|
|
||||||
|
String fail = null;
|
||||||
|
if (StringUtils.isBlank(stu.getName())) {
|
||||||
|
fail = "姓名不能为空";
|
||||||
|
} else if (stu.getClassId() == null) {
|
||||||
|
fail = "classId不能为空";
|
||||||
|
}
|
||||||
|
if (fail == null && StringUtils.isNotBlank(stu.getAccount())) {
|
||||||
|
String account = stu.getAccount().trim();
|
||||||
|
r.setAccount(account);
|
||||||
|
// 幂等查重①:account 全库查询(非本校范围)
|
||||||
|
Optional<SchStudent> exist = schStudentRepository.findByAccount(account);
|
||||||
|
if (exist.isPresent()) {
|
||||||
|
SchStudent s = exist.get();
|
||||||
|
if (!schId.equals(s.getSchId())) {
|
||||||
|
fail = "账号已被其他学校占用";
|
||||||
|
} else if (Objects.equals(stu.getClassId(), s.getClassId())) {
|
||||||
|
r.setOk(true);
|
||||||
|
r.setStuId(s.getStuId());
|
||||||
|
r.setReason("已存在");
|
||||||
|
results.add(r);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
fail = "账号已存在于其他班级";
|
||||||
|
}
|
||||||
|
} else if (!batchAccounts.add(account)) {
|
||||||
|
fail = "本批账号重复";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fail == null) {
|
||||||
|
// 幂等查重②:班内姓名唯一(含本批)
|
||||||
|
String nameKey = stu.getClassId() + "_" + stu.getName();
|
||||||
|
if (schStudentRepository.existsBySchIdAndClassIdAndName(schId, stu.getClassId(), stu.getName())) {
|
||||||
|
r.setOk(true);
|
||||||
|
r.setReason("班内同名已存在");
|
||||||
|
results.add(r);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!batchNameKeys.add(nameKey)) {
|
||||||
|
fail = "本批班级内姓名重复";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fail != null) {
|
||||||
|
r.setOk(false);
|
||||||
|
r.setReason(fail);
|
||||||
|
results.add(r);
|
||||||
|
if (allOrNothing) {
|
||||||
|
rollbackAllOrNothing(syncedList, syncedGroupIds, schId, "整批失败(all-or-nothing):" + stu.getName() + " " + fail);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:先收集不入库,尾部统一 saveAll
|
||||||
|
SchStudent entity = new SchStudent();
|
||||||
|
entity.setSchId(schId);
|
||||||
|
entity.setName(stu.getName());
|
||||||
|
entity.setGender(stu.getGender());
|
||||||
|
entity.setAccount(r.getAccount());
|
||||||
|
entity.setStuClassNo(stu.getStuClassNo());
|
||||||
|
entity.setGroupId(stu.getClassId());
|
||||||
|
entity.setClassId(stu.getClassId());
|
||||||
|
entity.setTransferStatus(TransferStatus.IN.getId());
|
||||||
|
entity.setAbnormalStatus(StuAbnormalStatus.NORMAL.getId());
|
||||||
|
entity.setStatus(UserStatus.NORMAL.getId());
|
||||||
|
entity.setSort(sortOf(stu.getStuClassNo()));
|
||||||
|
entity.setCreateDate(now);
|
||||||
|
entity.setUpdateDate(now);
|
||||||
|
try {
|
||||||
|
BoaUser boaUser = sendUserBaseService.addStudent(entity, stu.getPwd());
|
||||||
|
entity.setStuId(boaUser.getId());
|
||||||
|
saveList.add(entity);
|
||||||
|
syncedList.add(boaUser);
|
||||||
|
syncedGroupIds.add(stu.getClassId());
|
||||||
|
r.setOk(true);
|
||||||
|
r.setStuId(boaUser.getId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[addClassStudent] 建号失败 schId={}, name={}, account={}", schId, stu.getName(), r.getAccount(), e);
|
||||||
|
r.setOk(false);
|
||||||
|
String msg = e.getMessage();
|
||||||
|
r.setReason(msg == null || msg.isEmpty() ? e.getClass().getSimpleName() : msg);
|
||||||
|
results.add(r);
|
||||||
|
if (allOrNothing) {
|
||||||
|
rollbackAllOrNothing(syncedList, syncedGroupIds, schId, "整批失败(all-or-nothing):" + stu.getName() + " " + r.getReason());
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
results.add(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!saveList.isEmpty()) {
|
||||||
|
schStudentRepository.saveAll(saveList);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 落库失败属批次级失败:all-or-nothing 删已建账号;聚合模式保留账号(重推按 account 幂等补齐,避免删号/重建抖动)
|
||||||
|
log.error("[addClassStudent] 学生落库失败 schId={}, 已建账号 {} 个", schId, syncedList.size(), e);
|
||||||
|
if (allOrNothing) {
|
||||||
|
rollbackAllOrNothing(syncedList, syncedGroupIds, schId, "整批失败(all-or-nothing):学生落库失败 " + e.getMessage());
|
||||||
|
}
|
||||||
|
throw new DException("学生落库失败(已建账号保留,可重推补齐):" + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
studentCacheService.clearClassStudentCaches(schId, classIdSet);
|
||||||
|
|
||||||
|
long okCount = results.stream().filter(x -> Boolean.TRUE.equals(x.getOk())).count();
|
||||||
|
log.info("[addClassStudent] schId={}, 学生 {} 人:成功 {} 失败 {}(allOrNothing={})",
|
||||||
|
schId, results.size(), okCount, results.size() - okCount, allOrNothing);
|
||||||
|
SyncStudentResultVO vo = new SyncStudentResultVO();
|
||||||
|
vo.setResults(results);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** all-or-nothing 失败出口:删本批已建平台账号后整批报错(本地事务由 @Transactional 回滚) */
|
||||||
|
private void rollbackAllOrNothing(List<BoaUser> syncedList, List<Integer> syncedGroupIds,
|
||||||
|
Integer schId, String message) {
|
||||||
|
sendUserBaseService.rollbackSyncedStudents(syncedList, syncedGroupIds, schId);
|
||||||
|
throw new DException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 学号数字部分 → 排序(与导入模板口径一致) */
|
||||||
|
private Integer sortOf(String stuClassNo) {
|
||||||
|
if (StringUtils.isBlank(stuClassNo)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String digits = stuClassNo.replaceAll("\\D+", "");
|
||||||
|
if (StringUtils.isNotBlank(digits)) {
|
||||||
|
return Integer.parseInt(digits);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+93
@@ -0,0 +1,93 @@
|
|||||||
|
package com.yunhai.bschclassmanage.service.impl.teacher;
|
||||||
|
|
||||||
|
import com.yunhai.bschclassmanage.Repository.SchTeacherGroupRepository;
|
||||||
|
import com.yunhai.bschclassmanage.Repository.SchTeacherRepository;
|
||||||
|
import com.yunhai.bschclassmanage.entity.SchTeacher;
|
||||||
|
import com.yunhai.bschclassmanage.entity.SchTeacherGroup;
|
||||||
|
import com.yunhai.bschclassmanage.model.dto.TeacherBaseInfoDTO;
|
||||||
|
import com.yunhai.bschclassmanage.annotation.SyncOpLog;
|
||||||
|
import com.yunhai.bschclassmanage.enums.SyncDataType;
|
||||||
|
import com.yunhai.bschclassmanage.service.ISendUserBaseService;
|
||||||
|
import com.yunhai.platform.utils.model.BoaUser;
|
||||||
|
import com.yunhai.platform.utils.exception.DException;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/** 开放接口只修改四项资料,经既有教师同步通道(SendUserBaseService)推送用户中心。 */
|
||||||
|
@Service
|
||||||
|
public class SchTeacherBaseInfoService {
|
||||||
|
@Autowired private SchTeacherRepository teacherRepository;
|
||||||
|
@Autowired private SchTeacherGroupRepository groupRepository;
|
||||||
|
@Autowired private ISendUserBaseService sendUserBaseService;
|
||||||
|
@Autowired private SchTeacherCacheService cacheService;
|
||||||
|
|
||||||
|
public TeacherBaseInfoDTO get(Integer schId, Integer teaId) {
|
||||||
|
requireMembership(schId, teaId);
|
||||||
|
return toDto(schId, teachers(teaId).get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
@SyncOpLog(SyncDataType.SEND_EDIT_TEACHER)
|
||||||
|
public TeacherBaseInfoDTO edit(TeacherBaseInfoDTO dto) {
|
||||||
|
List<SchTeacherGroup> memberships = requireMembership(dto.getSchId(), dto.getTeaId());
|
||||||
|
List<SchTeacher> teachers = teachers(dto.getTeaId());
|
||||||
|
String name = dto.getName() == null ? "" : dto.getName().trim();
|
||||||
|
if (name.isEmpty()) throw new DException("姓名不能为空");
|
||||||
|
// 同名规则与管理端一致,排除教师自己的所有本地记录。
|
||||||
|
List<Integer> ids = groupRepository.findTeaIdsBySchId(dto.getSchId());
|
||||||
|
if (teacherRepository.findByTeaIdIn(ids).stream()
|
||||||
|
.anyMatch(t -> !dto.getTeaId().equals(t.getTeaId()) && name.equals(t.getName()))) {
|
||||||
|
throw new DException("该校下已存在同名教师:" + name);
|
||||||
|
}
|
||||||
|
for (SchTeacher teacher : teachers) {
|
||||||
|
teacher.setName(name);
|
||||||
|
teacher.setGender(dto.getGender());
|
||||||
|
teacher.setIdCard(trim(dto.getIdcard()));
|
||||||
|
teacher.setTel(trim(dto.getPhone()));
|
||||||
|
teacher.setUpdateDate(new Date());
|
||||||
|
teacher.setUpdateUserId(dto.getTeaId());
|
||||||
|
}
|
||||||
|
// 先同步;失败抛异常并回滚本地事务。teaId 可能对应多行,统一修改。
|
||||||
|
// 复用管理端同款教师同步(用户中心 /oapi/user/edit/{id},身份追加语义、不改密码);
|
||||||
|
// groupId 仅用于解析学校范围,不会移动教师分组。
|
||||||
|
BoaUser synced = sendUserBaseService.editTeacher(teachers.get(0), null, memberships.get(0).getGroupId());
|
||||||
|
if (synced == null || !dto.getTeaId().equals(synced.getId())) {
|
||||||
|
throw new DException("用户中心未返回有效教师信息,同步失败");
|
||||||
|
}
|
||||||
|
teacherRepository.saveAll(teachers);
|
||||||
|
Set<Integer> schools = groupRepository.findByTeaIdIn(Collections.singletonList(dto.getTeaId())).stream()
|
||||||
|
.map(SchTeacherGroup::getSchId).collect(Collectors.toSet());
|
||||||
|
schools.add(dto.getSchId());
|
||||||
|
cacheService.clearTeacherCaches(schools);
|
||||||
|
return toDto(dto.getSchId(), teachers.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SchTeacherGroup> requireMembership(Integer schId, Integer teaId) {
|
||||||
|
if (schId == null || schId <= 0 || teaId == null || teaId <= 0) {
|
||||||
|
throw new DException("学校和教师平台用户ID不能为空");
|
||||||
|
}
|
||||||
|
List<SchTeacherGroup> memberships = groupRepository.findByTeaIdAndSchId(teaId, schId).stream()
|
||||||
|
.filter(g -> Integer.valueOf(0).equals(g.getTransferStatus())).collect(Collectors.toList());
|
||||||
|
if (memberships.isEmpty()) throw new DException("教师不属于指定学校或已转出");
|
||||||
|
return memberships;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SchTeacher> teachers(Integer teaId) {
|
||||||
|
List<SchTeacher> teachers = teacherRepository.findByTeaId(teaId);
|
||||||
|
if (teachers.isEmpty()) throw new DException("教师不存在");
|
||||||
|
return teachers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TeacherBaseInfoDTO toDto(Integer schId, SchTeacher teacher) {
|
||||||
|
TeacherBaseInfoDTO dto = new TeacherBaseInfoDTO();
|
||||||
|
dto.setSchId(schId); dto.setTeaId(teacher.getTeaId()); dto.setName(teacher.getName());
|
||||||
|
dto.setGender(teacher.getGender()); dto.setIdcard(teacher.getIdCard()); dto.setPhone(teacher.getTel());
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String trim(String value) { return value == null || value.trim().isEmpty() ? null : value.trim(); }
|
||||||
|
}
|
||||||
+1
-1
@@ -191,7 +191,7 @@ public class SchTeacherRelationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 异步记录操作日志
|
// 异步记录操作日志
|
||||||
schTeacherRepository.findByTeaId(teaId).ifPresent(teacher ->
|
schTeacherRepository.findByTeaId(teaId).stream().findFirst().ifPresent(teacher ->
|
||||||
logService.setTransferInLogs(user, Collections.singletonList(teacher), targetGroupId));
|
logService.setTransferInLogs(user, Collections.singletonList(teacher), targetGroupId));
|
||||||
|
|
||||||
// 清除所有涉及学校的缓存
|
// 清除所有涉及学校的缓存
|
||||||
|
|||||||
+2
-2
@@ -133,8 +133,8 @@ public class SchClassTeachQueryService {
|
|||||||
return buildEmptyResult();
|
return buildEmptyResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 获取学科数据
|
// 2. 获取学科数据(仅启用的学科)
|
||||||
Map<String, String> subjectNameMap = schDicSubjectService.toIdAndNameMap(schId, schsystemId);
|
Map<String, String> subjectNameMap = schDicSubjectService.toEnableIdAndNameMap(schId, schsystemId);
|
||||||
if (subjectNameMap == null || subjectNameMap.isEmpty()) {
|
if (subjectNameMap == null || subjectNameMap.isEmpty()) {
|
||||||
return buildEmptyResult();
|
return buildEmptyResult();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ spring:
|
|||||||
yh:
|
yh:
|
||||||
sso:
|
sso:
|
||||||
server-url-prefix: http://sso.yunhaixingtong.com
|
server-url-prefix: http://sso.yunhaixingtong.com
|
||||||
client-host-url: http://192.168.10.166:8086
|
client-host-url: http://localhost:8080
|
||||||
allow-headers: all
|
allow-headers: all
|
||||||
allow-origins: all
|
allow-origins: all
|
||||||
url-pattern: /api/*
|
url-pattern: /api/*
|
||||||
@@ -84,11 +84,17 @@ yh:
|
|||||||
# 本应用自身身份(客户端签名用)
|
# 本应用自身身份(客户端签名用)
|
||||||
app-key: 100002
|
app-key: 100002
|
||||||
app-secret: 2314a3b158b24a838b2b9b69fd3db118
|
app-secret: 2314a3b158b24a838b2b9b69fd3db118
|
||||||
|
# 调用方信任名单:分班管理(AcaClassAssign,/oapi/class/AddClass|AddClassStudent)。
|
||||||
|
# 注意:app-auth 见 X-App-Key 即校验,未登记的 appKey 一律 401(实测 2026-10-21)。
|
||||||
|
# 生产发布需在 Nacos 同步登记该 appKey/secret,并对 /oapi/class/* 启用 enforce-patterns。
|
||||||
|
trusted-apps:
|
||||||
|
"100026": "73c08fceae3b48788358195760bf5977"
|
||||||
|
server:
|
||||||
|
# 分班推送写接口强制验签;只读 /oapi/sch/* 维持匿名(既有调用方不受影响)
|
||||||
|
enforce-patterns: [/oapi/class/*]
|
||||||
open:
|
open:
|
||||||
enabled: true
|
# 四域服务目录零配置(userbase/userclass/usermsg/flowcenter 枚举兜底 = base-host + 默认 ctx)
|
||||||
base-host: http://base.yunhaixingtong.com
|
base-host: http://base.yunhaixingtong.com
|
||||||
user-center-url: http://base.yunhaixingtong.com/userbase
|
|
||||||
group-center-url: http://base.yunhaixingtong.com/userbase
|
|
||||||
|
|
||||||
FILE_PATH: D://工作区/JAVA/bsch/
|
FILE_PATH: D://工作区/JAVA/bsch/
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
<file>/app/logs/app.log</file>
|
<file>/app/logs/app.log</file>
|
||||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
<fileNamePattern>/app/logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
|
<fileNamePattern>/app/logs/app.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
|
||||||
<maxHistory>30</maxHistory>
|
<maxHistory>30</maxHistory>
|
||||||
<totalSizeCap>5GB</totalSizeCap>
|
<totalSizeCap>5GB</totalSizeCap>
|
||||||
</rollingPolicy>
|
</rollingPolicy>
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package com.yunhai.bschclassmanage;
|
||||||
|
|
||||||
|
import com.yunhai.bschclassmanage.service.IStudentClassHistoryService;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本地复现 api/history/getHistoryClassTeachByStuId?stuId=10068 的内部服务错误。
|
||||||
|
* 直接调用 service 层(绕过 SSO 过滤器),打印真实异常堆栈。
|
||||||
|
*/
|
||||||
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
|
||||||
|
class ReproduceHistoryTeachByStuIdTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IStudentClassHistoryService studentClassHistoryService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void reproduce() {
|
||||||
|
try {
|
||||||
|
// 模拟真实 HTTP 场景:UserContextInterceptor.resolveUser 恒返回 null,schId 为 null
|
||||||
|
Object result = studentClassHistoryService.getHistoryClassTeachByStuId(null, 10068);
|
||||||
|
System.out.println("=== SUCCESS (schId=null) ===");
|
||||||
|
System.out.println(result);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
System.out.println("=== EXCEPTION (schId=null): " + e.getClass().getName() + " ===");
|
||||||
|
System.out.println("message: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private com.yunhai.bschclassmanage.utils.RedisUtil redisUtil;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private com.yunhai.bschclassmanage.Repository.SchStudentRepository schStudentRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回归用例:同一 stu_id 在 sch_student 存在多行(学生在多所学校各有一条就学记录)。
|
||||||
|
* 修复前 findByStuId 返回 Optional,会抛 NonUniqueResultException(线上表现为 code 6 内部服务错误)。
|
||||||
|
* 修复后应取当前学校那一行正常返回。@Transactional 保证测试数据回滚。
|
||||||
|
*/
|
||||||
|
@org.springframework.transaction.annotation.Transactional
|
||||||
|
@Test
|
||||||
|
void duplicateStuIdShouldNotCrash() {
|
||||||
|
// 本地库里 stu_id=10068 已有一行(sch_id=3, class_id=298),再造一行别校的记录
|
||||||
|
com.yunhai.bschclassmanage.entity.SchStudent other = new com.yunhai.bschclassmanage.entity.SchStudent();
|
||||||
|
other.setStuId(10068);
|
||||||
|
other.setSchId(999);
|
||||||
|
other.setClassId(888);
|
||||||
|
other.setName("重复记录-别校");
|
||||||
|
schStudentRepository.save(other);
|
||||||
|
schStudentRepository.flush();
|
||||||
|
|
||||||
|
// 当前学校 schId=3 的记录应被正确选中,不再抛 NonUniqueResultException
|
||||||
|
Object result = studentClassHistoryService.getHistoryClassTeachByStuId(3, 10068);
|
||||||
|
System.out.println("=== DUPLICATE stu_id RESULT ===");
|
||||||
|
System.out.println(result);
|
||||||
|
if (result == null) {
|
||||||
|
throw new AssertionError("expected non-null result");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 换成重复行所在学校 schId=999:实时表命中但班级不存在 → 应返回业务异常而不是内部错误
|
||||||
|
try {
|
||||||
|
studentClassHistoryService.getHistoryClassTeachByStuId(999, 10068);
|
||||||
|
System.out.println("=== schId=999 unexpectedly succeeded ===");
|
||||||
|
} catch (com.yunhai.platform.utils.exception.DException e) {
|
||||||
|
System.out.println("=== schId=999 DException (expected): " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void poisonCacheRead() {
|
||||||
|
for (String key : new String[]{
|
||||||
|
"bsch.classmanage.system_yearterm_poison",
|
||||||
|
"bsch.classmanage.system_yearterm_poison2"}) {
|
||||||
|
try {
|
||||||
|
Object raw = redisUtil.get(key, Object.class);
|
||||||
|
System.out.println("=== RAW GET [" + key + "]: " + (raw == null ? "null" : raw.getClass().getName()));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
System.out.println("=== RAW GET EXCEPTION [" + key + "]: " + e.getClass().getName() + " ===");
|
||||||
|
System.out.println("message: " + e.getMessage());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Object list = redisUtil.getList(key, com.yunhai.bschclassmanage.entity.BaseYearTerm.class);
|
||||||
|
System.out.println("=== GET LIST [" + key + "]: " + list);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
System.out.println("=== GET LIST EXCEPTION [" + key + "]: " + e.getClass().getName() + " ===");
|
||||||
|
System.out.println("message: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void reproduceWithSchId() {
|
||||||
|
try {
|
||||||
|
Object result = studentClassHistoryService.getHistoryClassTeachByStuId(3, 10068);
|
||||||
|
System.out.println("=== SUCCESS (schId=3) ===");
|
||||||
|
System.out.println(result);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
System.out.println("=== EXCEPTION (schId=3): " + e.getClass().getName() + " ===");
|
||||||
|
System.out.println("message: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+114
@@ -0,0 +1,114 @@
|
|||||||
|
package com.yunhai.bschclassmanage.controller.oapi.sch;
|
||||||
|
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.sch.OapiGradeNameVO;
|
||||||
|
import com.yunhai.bschclassmanage.service.IBaseSchsystemGradeService;
|
||||||
|
import com.yunhai.bschclassmanage.service.IBaseSchsystemService;
|
||||||
|
import com.yunhai.bschclassmanage.service.ISchBinSchsystemService;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/** getGradeName 开放接口单测:学段→学制匹配、学年反查年级字典名、无匹配返回 null */
|
||||||
|
class OapiGetGradeNameTest {
|
||||||
|
|
||||||
|
private OapiSchClassController controller;
|
||||||
|
private ISchBinSchsystemService schBinSchsystemService;
|
||||||
|
private IBaseSchsystemService baseSchsystemService;
|
||||||
|
private IBaseSchsystemGradeService baseSchsystemGradeService;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
controller = new OapiSchClassController();
|
||||||
|
schBinSchsystemService = Mockito.mock(ISchBinSchsystemService.class);
|
||||||
|
baseSchsystemService = Mockito.mock(IBaseSchsystemService.class);
|
||||||
|
baseSchsystemGradeService = Mockito.mock(IBaseSchsystemGradeService.class);
|
||||||
|
ReflectionTestUtils.setField(controller, "schClassRepository",
|
||||||
|
Mockito.mock(com.yunhai.bschclassmanage.Repository.SchClassRepository.class));
|
||||||
|
ReflectionTestUtils.setField(controller, "schInfoRepository",
|
||||||
|
Mockito.mock(com.yunhai.bschclassmanage.Repository.SchInfoRepository.class));
|
||||||
|
ReflectionTestUtils.setField(controller, "schClassService",
|
||||||
|
Mockito.mock(com.yunhai.bschclassmanage.service.ISchClassService.class));
|
||||||
|
ReflectionTestUtils.setField(controller, "schTeacherService",
|
||||||
|
Mockito.mock(com.yunhai.bschclassmanage.service.ISchTeacherService.class));
|
||||||
|
ReflectionTestUtils.setField(controller, "schBinSchsystemService", schBinSchsystemService);
|
||||||
|
ReflectionTestUtils.setField(controller, "baseSchsystemService", baseSchsystemService);
|
||||||
|
ReflectionTestUtils.setField(controller, "baseSchsystemGradeService", baseSchsystemGradeService);
|
||||||
|
|
||||||
|
// 云海小学:双学段(小学+初中),各自年级字典与入学年份映射
|
||||||
|
when(schBinSchsystemService.getSchBinSchsystemIds(27)).thenReturn(Arrays.asList(5, 9));
|
||||||
|
when(baseSchsystemService.getAcademicStageName(5)).thenReturn("小学");
|
||||||
|
when(baseSchsystemService.getAcademicStageName(9)).thenReturn("初中");
|
||||||
|
|
||||||
|
Map<Integer, Integer> primaryYears = new LinkedHashMap<>();
|
||||||
|
primaryYears.put(1, 2026);
|
||||||
|
primaryYears.put(2, 2025);
|
||||||
|
primaryYears.put(3, 2024);
|
||||||
|
when(baseSchsystemGradeService.getNowYearGradeMap(27, 5)).thenReturn(primaryYears);
|
||||||
|
Map<Integer, String> primaryNames = new HashMap<>();
|
||||||
|
primaryNames.put(1, "小一");
|
||||||
|
primaryNames.put(2, "小二");
|
||||||
|
primaryNames.put(3, "小三");
|
||||||
|
when(baseSchsystemGradeService.getGradeNameMap(27, 5, null)).thenReturn(primaryNames);
|
||||||
|
|
||||||
|
Map<Integer, Integer> juniorYears = new LinkedHashMap<>();
|
||||||
|
juniorYears.put(7, 2026);
|
||||||
|
juniorYears.put(8, 2025);
|
||||||
|
when(baseSchsystemGradeService.getNowYearGradeMap(27, 9)).thenReturn(juniorYears);
|
||||||
|
Map<Integer, String> juniorNames = new HashMap<>();
|
||||||
|
juniorNames.put(7, "初一");
|
||||||
|
juniorNames.put(8, "初二");
|
||||||
|
when(baseSchsystemGradeService.getGradeNameMap(27, 9, null)).thenReturn(juniorNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void resolvesGradeNameByStageAndYear() {
|
||||||
|
// 小学 2026 级入学 → 小一(学校字典真实名称)
|
||||||
|
OapiGradeNameVO vo = controller.getGradeName(27, 2026, 1).getBody();
|
||||||
|
assertEquals(1, vo.getGradeId());
|
||||||
|
assertEquals("小一", vo.getGradeName());
|
||||||
|
assertEquals(2026, vo.getYear());
|
||||||
|
|
||||||
|
// 初中 2026 → 初一(同校多学段按 stage 区分)
|
||||||
|
assertEquals("初一", controller.getGradeName(27, 2026, 2).getBody().getGradeName());
|
||||||
|
|
||||||
|
// 低年级已升学:2024 入学的小学年级 → 小三
|
||||||
|
assertEquals("小三", controller.getGradeName(27, 2024, 1).getBody().getGradeName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void returnsNullWhenNoMatch() {
|
||||||
|
// 年份无对应年级(超出字典范围)
|
||||||
|
assertNull(controller.getGradeName(27, 2020, 1).getBody());
|
||||||
|
// 学段在学校未开设(高中不存在)
|
||||||
|
assertNull(controller.getGradeName(27, 2026, 3).getBody());
|
||||||
|
// 参数缺失
|
||||||
|
assertNull(controller.getGradeName(null, 2026, 1).getBody());
|
||||||
|
assertNull(controller.getGradeName(27, null, 1).getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void skipsBrokenSchsystemAndContinues() {
|
||||||
|
// 单个学制学段名缺失(学制未找到等)跳过,不影响其余学制匹配
|
||||||
|
when(baseSchsystemService.getAcademicStageName(5)).thenThrow(new RuntimeException("学制未找到!"));
|
||||||
|
assertEquals("初一", controller.getGradeName(27, 2026, 2).getBody().getGradeName());
|
||||||
|
assertNull(controller.getGradeName(27, 2026, 1).getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void emptyYearMapReturnsNull() {
|
||||||
|
when(baseSchsystemGradeService.getNowYearGradeMap(eq(27), anyInt())).thenReturn(new HashMap<>());
|
||||||
|
assertNull(controller.getGradeName(27, 2026, 1).getBody());
|
||||||
|
}
|
||||||
|
}
|
||||||
+77
@@ -0,0 +1,77 @@
|
|||||||
|
package com.yunhai.bschclassmanage.service.impl.teacher;
|
||||||
|
|
||||||
|
import com.yunhai.bschclassmanage.Repository.*;
|
||||||
|
import com.yunhai.bschclassmanage.entity.*;
|
||||||
|
import com.yunhai.bschclassmanage.model.dto.TeacherBaseInfoDTO;
|
||||||
|
import com.yunhai.bschclassmanage.service.ISendUserBaseService;
|
||||||
|
import com.yunhai.platform.utils.exception.DException;
|
||||||
|
import com.yunhai.platform.utils.model.BoaUser;
|
||||||
|
import org.junit.jupiter.api.*;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.*;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import java.util.*;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.*;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class SchTeacherBaseInfoServiceTest {
|
||||||
|
@InjectMocks private SchTeacherBaseInfoService service;
|
||||||
|
@Mock private SchTeacherRepository teacherRepository;
|
||||||
|
@Mock private SchTeacherGroupRepository groupRepository;
|
||||||
|
@Mock private ISendUserBaseService sendUserBaseService;
|
||||||
|
@Mock private SchTeacherCacheService cacheService;
|
||||||
|
|
||||||
|
private SchTeacherGroup group(int school) {
|
||||||
|
SchTeacherGroup g = new SchTeacherGroup();
|
||||||
|
g.setSchId(school); g.setTeaId(101); g.setGroupId(31); g.setTransferStatus(0);
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
private TeacherBaseInfoDTO request() {
|
||||||
|
TeacherBaseInfoDTO d = new TeacherBaseInfoDTO();
|
||||||
|
d.setSchId(3); d.setTeaId(101); d.setName("新姓名"); d.setGender(1); d.setPhone("");
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
private List<SchTeacher> givenTeacher() {
|
||||||
|
when(groupRepository.findByTeaIdAndSchId(101, 3)).thenReturn(Collections.singletonList(group(3)));
|
||||||
|
SchTeacher a = new SchTeacher(); a.setId(11); a.setTeaId(101); a.setAccount("teacher"); a.setStatus(1);
|
||||||
|
SchTeacher b = new SchTeacher(); b.setId(12); b.setTeaId(101); b.setAccount("teacher"); b.setStatus(1);
|
||||||
|
List<SchTeacher> rows = Arrays.asList(a,b);
|
||||||
|
when(teacherRepository.findByTeaId(101)).thenReturn(rows);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test void updatesAllTeacherRowsAndInvalidatesAllSchoolsAfterSync() {
|
||||||
|
List<SchTeacher> rows = givenTeacher();
|
||||||
|
when(sendUserBaseService.editTeacher(any(SchTeacher.class), isNull(), eq(31)))
|
||||||
|
.thenReturn(new BoaUser(101, "teacher", "新姓名"));
|
||||||
|
when(groupRepository.findByTeaIdIn(Collections.singletonList(101))).thenReturn(Arrays.asList(group(3),group(4)));
|
||||||
|
assertEquals("新姓名", service.edit(request()).getName());
|
||||||
|
for (SchTeacher t : rows) {
|
||||||
|
assertEquals("新姓名", t.getName()); assertEquals(1,t.getGender());
|
||||||
|
assertNull(t.getTel()); assertEquals("teacher",t.getAccount()); assertEquals(1,t.getStatus());
|
||||||
|
}
|
||||||
|
InOrder order = inOrder(sendUserBaseService,teacherRepository,cacheService);
|
||||||
|
order.verify(sendUserBaseService).editTeacher(any(SchTeacher.class), isNull(), eq(31));
|
||||||
|
order.verify(teacherRepository).saveAll(rows);
|
||||||
|
order.verify(cacheService).clearTeacherCaches(new HashSet<>(Arrays.asList(3,4)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test void rejectsWrongSchoolAndTransferredOutTeacher() {
|
||||||
|
SchTeacherGroup out = group(3); out.setTransferStatus(1);
|
||||||
|
when(groupRepository.findByTeaIdAndSchId(101,3)).thenReturn(Collections.singletonList(out));
|
||||||
|
assertThrows(DException.class, () -> service.edit(request()));
|
||||||
|
verifyNoInteractions(sendUserBaseService,teacherRepository,cacheService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test void syncFailureDoesNotSaveOrClearCache() {
|
||||||
|
List<SchTeacher> rows = givenTeacher();
|
||||||
|
when(sendUserBaseService.editTeacher(any(SchTeacher.class), isNull(), eq(31)))
|
||||||
|
.thenThrow(new DException("平台失败"));
|
||||||
|
assertThrows(DException.class, () -> service.edit(request()));
|
||||||
|
verify(teacherRepository,never()).saveAll(any()); verifyNoInteractions(cacheService);
|
||||||
|
// 同步前已就地改写的实体随事务回滚,不落库
|
||||||
|
assertEquals("新姓名", rows.get(0).getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user