From be011dd4f119ca3fd391542eb62748bd53365d43 Mon Sep 17 00:00:00 2001 From: weicw <594098497@qq.com> Date: Tue, 3 Nov 2026 23:13:41 +0800 Subject: [PATCH] =?UTF-8?q?fix(gxmu):=20=E6=96=B0=E7=94=B3=E6=8A=A5?= =?UTF-8?q?=E6=8C=89=E5=AD=A6=E7=B1=8D=E8=A1=A5=E9=BD=90=20college=5Fid?= =?UTF-8?q?=EF=BC=8C=E4=B8=8D=E5=86=8D=E5=8F=AA=E6=9C=89=E5=AD=98=E9=87=8F?= =?UTF-8?q?=E5=9B=9E=E5=A1=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 4 张在跑表(gxmu_sjqn / gxmu_tzbcy_form / gxmu_qmgc_form / gxmu_wxxzx_project) 的实体补 collegeId,save 路径在为空时按「登录用户 → gxmu_student.college_id」 补齐;解析不到(无学籍、名册未同步、学院不唯一)一律留空不报错。 更新路径不调用解析器,collegeId 为 null 时 MyBatis-Plus 忽略该字段,库里已有值不变。 解析逻辑收在一个共享组件 CollegeResolver 里,4 个 controller 只各调一行; 口径与一次性回填脚本一致(优先在校那条学籍,学院不唯一就留空)。 单测:有学籍时写入、无学籍时留空、学院多义时留空、更新不覆盖已有值。 refs #58 --- .../gxmu/controller/QmgcFormController.java | 6 + .../gxmu/controller/SjqnFormController.java | 6 + .../gxmu/controller/TzbcyFormController.java | 6 + .../gxmu/controller/WxxzxFormController.java | 6 + .../com/gxwebsoft/gxmu/entity/QmgcForm.java | 3 + .../com/gxwebsoft/gxmu/entity/SjqnForm.java | 3 + .../com/gxwebsoft/gxmu/entity/TzbcyForm.java | 3 + .../com/gxwebsoft/gxmu/entity/WxxzxForm.java | 3 + .../gxmu/service/CollegeResolver.java | 34 ++++++ .../service/impl/CollegeResolverImpl.java | 62 ++++++++++ .../service/impl/CollegeResolverImplTest.java | 106 ++++++++++++++++++ 11 files changed, 238 insertions(+) create mode 100644 src/main/java/com/gxwebsoft/gxmu/service/CollegeResolver.java create mode 100644 src/main/java/com/gxwebsoft/gxmu/service/impl/CollegeResolverImpl.java create mode 100644 src/test/java/com/gxwebsoft/gxmu/service/impl/CollegeResolverImplTest.java diff --git a/src/main/java/com/gxwebsoft/gxmu/controller/QmgcFormController.java b/src/main/java/com/gxwebsoft/gxmu/controller/QmgcFormController.java index 82c06ef..20dcf33 100644 --- a/src/main/java/com/gxwebsoft/gxmu/controller/QmgcFormController.java +++ b/src/main/java/com/gxwebsoft/gxmu/controller/QmgcFormController.java @@ -15,6 +15,7 @@ import com.gxwebsoft.gxmu.param.QmgcFormParam; import com.gxwebsoft.gxmu.service.QmgcFormService; import com.gxwebsoft.gxmu.service.ReviewFlowStarterService; import com.gxwebsoft.gxmu.model.DeclareCheckResult; +import com.gxwebsoft.gxmu.service.CollegeResolver; import com.gxwebsoft.gxmu.service.DeclareGuardService; import org.springframework.transaction.annotation.Transactional; import com.gxwebsoft.gxmu.util.GxmuQueryHelper; @@ -42,6 +43,8 @@ public class QmgcFormController extends BaseController { @Resource private QmgcFormService qmgcFormService; @Resource + private CollegeResolver collegeResolver; + @Resource private ReviewFlowStarterService reviewFlowStarterService; @Resource private DeclareGuardService declareGuardService; @@ -145,6 +148,9 @@ public class QmgcFormController extends BaseController { if (loginUser != null) { qmgcForm.setUserId(loginUser.getUserId()); } + // 新申报没有学院标识时按学籍补齐(没有学籍 / 学院不唯一就留空,不报错): + // 审批链的 COLLEGE_ROLE 规则要靠它定位学院,一次性回填只解决存量 + qmgcForm.setCollegeId(collegeResolver.fillIfAbsent(qmgcForm.getCollegeId(), qmgcForm.getUserId())); DeclareCheckResult declareCheck = declareGuardService.checkBeforeCreate("gxmu_qmgc_form", qmgcForm.getDeclareId(), qmgcForm.getYear(), qmgcForm.getUserId()); if (declareCheck.isRejected()) { diff --git a/src/main/java/com/gxwebsoft/gxmu/controller/SjqnFormController.java b/src/main/java/com/gxwebsoft/gxmu/controller/SjqnFormController.java index 38d9bf9..cf84fd1 100644 --- a/src/main/java/com/gxwebsoft/gxmu/controller/SjqnFormController.java +++ b/src/main/java/com/gxwebsoft/gxmu/controller/SjqnFormController.java @@ -16,6 +16,7 @@ import com.gxwebsoft.gxmu.entity.SjqnForm; import com.gxwebsoft.gxmu.param.SjqnFormParam; import com.gxwebsoft.gxmu.service.ReviewFlowStarterService; import com.gxwebsoft.gxmu.model.DeclareCheckResult; +import com.gxwebsoft.gxmu.service.CollegeResolver; import com.gxwebsoft.gxmu.service.DeclareGuardService; import org.springframework.transaction.annotation.Transactional; import com.gxwebsoft.gxmu.service.SjqnFormService; @@ -46,6 +47,8 @@ public class SjqnFormController extends BaseController { @Resource private SjqnFormService sjqnFormService; @Resource + private CollegeResolver collegeResolver; + @Resource private ReviewFlowStarterService reviewFlowStarterService; @Resource private DeclareGuardService declareGuardService; @@ -162,6 +165,9 @@ public class SjqnFormController extends BaseController { if (loginUser != null) { sjqnForm.setUserId(loginUser.getUserId()); } + // 新申报没有学院标识时按学籍补齐(没有学籍 / 学院不唯一就留空,不报错): + // 审批链的 COLLEGE_ROLE 规则要靠它定位学院,一次性回填只解决存量 + sjqnForm.setCollegeId(collegeResolver.fillIfAbsent(sjqnForm.getCollegeId(), sjqnForm.getUserId())); DeclareCheckResult declareCheck = declareGuardService.checkBeforeCreate("gxmu_sjqn", sjqnForm.getDeclareId(), sjqnForm.getYear(), sjqnForm.getUserId()); if (declareCheck.isRejected()) { diff --git a/src/main/java/com/gxwebsoft/gxmu/controller/TzbcyFormController.java b/src/main/java/com/gxwebsoft/gxmu/controller/TzbcyFormController.java index b935226..00603f7 100644 --- a/src/main/java/com/gxwebsoft/gxmu/controller/TzbcyFormController.java +++ b/src/main/java/com/gxwebsoft/gxmu/controller/TzbcyFormController.java @@ -11,6 +11,7 @@ import com.gxwebsoft.gxmu.entity.TzbcyForm; import com.gxwebsoft.gxmu.model.TzbcyStatisticsResult; import com.gxwebsoft.gxmu.param.TzbcyFormParam; import com.gxwebsoft.gxmu.model.DeclareCheckResult; +import com.gxwebsoft.gxmu.service.CollegeResolver; import com.gxwebsoft.gxmu.service.DeclareGuardService; import com.gxwebsoft.gxmu.service.ReviewFlowStarterService; import org.springframework.transaction.annotation.Transactional; @@ -38,6 +39,8 @@ public class TzbcyFormController extends BaseController { @Resource private TzbcyFormService tzbcyFormService; @Resource + private CollegeResolver collegeResolver; + @Resource private ReviewFlowStarterService reviewFlowStarterService; @Resource private DeclareGuardService declareGuardService; @@ -99,6 +102,9 @@ public class TzbcyFormController extends BaseController { if (loginUser != null) { tzbcyForm.setUserId(loginUser.getUserId()); } + // 新申报没有学院标识时按学籍补齐(没有学籍 / 学院不唯一就留空,不报错): + // 审批链的 COLLEGE_ROLE 规则要靠它定位学院,一次性回填只解决存量 + tzbcyForm.setCollegeId(collegeResolver.fillIfAbsent(tzbcyForm.getCollegeId(), tzbcyForm.getUserId())); DeclareCheckResult declareCheck = declareGuardService.checkBeforeCreate("gxmu_tzbcy_form", tzbcyForm.getDeclareId(), tzbcyForm.getYear(), tzbcyForm.getUserId()); if (declareCheck.isRejected()) { diff --git a/src/main/java/com/gxwebsoft/gxmu/controller/WxxzxFormController.java b/src/main/java/com/gxwebsoft/gxmu/controller/WxxzxFormController.java index 46c7812..f6a7614 100644 --- a/src/main/java/com/gxwebsoft/gxmu/controller/WxxzxFormController.java +++ b/src/main/java/com/gxwebsoft/gxmu/controller/WxxzxFormController.java @@ -27,6 +27,7 @@ import com.gxwebsoft.gxmu.param.WxxzxFormParam; import com.gxwebsoft.gxmu.service.QmgcFormService; import com.gxwebsoft.gxmu.service.ReviewFlowStarterService; import com.gxwebsoft.gxmu.model.DeclareCheckResult; +import com.gxwebsoft.gxmu.service.CollegeResolver; import com.gxwebsoft.gxmu.service.DeclareGuardService; import org.springframework.transaction.annotation.Transactional; import com.gxwebsoft.gxmu.service.SjqnFormService; @@ -100,6 +101,8 @@ public class WxxzxFormController extends BaseController { @Resource private WxxzxFormService wxxzxFormService; @Resource + private CollegeResolver collegeResolver; + @Resource private SjqnFormService sjqnFormService; @Resource private SjtbzbsjFormService sjtbzbsjFormService; @@ -254,6 +257,9 @@ public class WxxzxFormController extends BaseController { if (loginUser != null) { wxxzxForm.setUserId(loginUser.getUserId()); } + // 新申报没有学院标识时按学籍补齐(没有学籍 / 学院不唯一就留空,不报错): + // 审批链的 COLLEGE_ROLE 规则要靠它定位学院,一次性回填只解决存量 + wxxzxForm.setCollegeId(collegeResolver.fillIfAbsent(wxxzxForm.getCollegeId(), wxxzxForm.getUserId())); DeclareCheckResult declareCheck = declareGuardService.checkBeforeCreate("gxmu_wxxzx_project", wxxzxForm.getDeclareId(), wxxzxForm.getYear(), wxxzxForm.getUserId()); if (declareCheck.isRejected()) { diff --git a/src/main/java/com/gxwebsoft/gxmu/entity/QmgcForm.java b/src/main/java/com/gxwebsoft/gxmu/entity/QmgcForm.java index ff60c4a..eb25330 100644 --- a/src/main/java/com/gxwebsoft/gxmu/entity/QmgcForm.java +++ b/src/main/java/com/gxwebsoft/gxmu/entity/QmgcForm.java @@ -117,6 +117,9 @@ public class QmgcForm implements Serializable { @ApiModelProperty("用户ID") private Integer userId; + @ApiModelProperty("学院标识(sys_organization.organization_id),新建申报时按学籍补齐,允许为空") + private Integer collegeId; + @ApiModelProperty("是否删除") @TableLogic private Integer deleted; diff --git a/src/main/java/com/gxwebsoft/gxmu/entity/SjqnForm.java b/src/main/java/com/gxwebsoft/gxmu/entity/SjqnForm.java index a9d3a0d..c91b9ff 100644 --- a/src/main/java/com/gxwebsoft/gxmu/entity/SjqnForm.java +++ b/src/main/java/com/gxwebsoft/gxmu/entity/SjqnForm.java @@ -92,6 +92,9 @@ public class SjqnForm implements Serializable { @ApiModelProperty("用户ID") private Integer userId; + @ApiModelProperty("学院标识(sys_organization.organization_id),新建申报时按学籍补齐,允许为空") + private Integer collegeId; + @ApiModelProperty("是否删除") @TableLogic private Integer deleted; diff --git a/src/main/java/com/gxwebsoft/gxmu/entity/TzbcyForm.java b/src/main/java/com/gxwebsoft/gxmu/entity/TzbcyForm.java index 8ac524b..22803cc 100644 --- a/src/main/java/com/gxwebsoft/gxmu/entity/TzbcyForm.java +++ b/src/main/java/com/gxwebsoft/gxmu/entity/TzbcyForm.java @@ -114,6 +114,9 @@ public class TzbcyForm implements Serializable { @ApiModelProperty("用户ID") private Integer userId; + @ApiModelProperty("学院标识(sys_organization.organization_id),新建申报时按学籍补齐,允许为空") + private Integer collegeId; + @ApiModelProperty("是否删除") @TableLogic private Integer deleted; diff --git a/src/main/java/com/gxwebsoft/gxmu/entity/WxxzxForm.java b/src/main/java/com/gxwebsoft/gxmu/entity/WxxzxForm.java index 3031b0c..a86d2d7 100644 --- a/src/main/java/com/gxwebsoft/gxmu/entity/WxxzxForm.java +++ b/src/main/java/com/gxwebsoft/gxmu/entity/WxxzxForm.java @@ -143,6 +143,9 @@ public class WxxzxForm implements Serializable { @ApiModelProperty("用户ID") private Integer userId; + @ApiModelProperty("学院标识(sys_organization.organization_id),新建申报时按学籍补齐,允许为空") + private Integer collegeId; + @ApiModelProperty("是否删除") @TableLogic private Integer deleted; diff --git a/src/main/java/com/gxwebsoft/gxmu/service/CollegeResolver.java b/src/main/java/com/gxwebsoft/gxmu/service/CollegeResolver.java new file mode 100644 index 0000000..4a319d5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/gxmu/service/CollegeResolver.java @@ -0,0 +1,34 @@ +package com.gxwebsoft.gxmu.service; + +/** + * 学院标识解析:把登录用户映射到申报记录的 college_id(spec #58 用户故事 2)。 + * + *
申报表上的 college_id 指向 sys_organization.organization_id,与学生名册 + * gxmu_student.college_id 是同一套 ID(见 ADR 0006「学院标识」)。新建申报时按 + * 「登录用户 → gxmu_student.college_id」补齐,审批链里的 COLLEGE_ROLE 规则才有字段可用; + * 解析不到(没有学籍、名册未同步、学院不唯一)一律返回 null,不报错、不猜。 + * + * @author Codex + * @since 2026-11-11 + */ +public interface CollegeResolver { + + /** + * 按登录用户查学院标识。 + * + *
同一用户有多条学籍时以「在校」那条为准;学院不唯一说明无法判断,返回 null。 + * + * @param userId 登录用户 id(sys_user.user_id),可为空 + * @return 学院标识,解析不到返回 null + */ + Integer resolve(Integer userId); + + /** + * 只在申报记录还没有学院标识时补齐;已有值一律保留(更新路径不覆盖已有值)。 + * + * @param currentCollegeId 申报记录当前的学院标识 + * @param userId 登录用户 id + * @return 最终写入的学院标识 + */ + Integer fillIfAbsent(Integer currentCollegeId, Integer userId); +} diff --git a/src/main/java/com/gxwebsoft/gxmu/service/impl/CollegeResolverImpl.java b/src/main/java/com/gxwebsoft/gxmu/service/impl/CollegeResolverImpl.java new file mode 100644 index 0000000..143ee79 --- /dev/null +++ b/src/main/java/com/gxwebsoft/gxmu/service/impl/CollegeResolverImpl.java @@ -0,0 +1,62 @@ +package com.gxwebsoft.gxmu.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.gxwebsoft.gxmu.openplat.entity.GxmuStudent; +import com.gxwebsoft.gxmu.openplat.mapper.GxmuStudentMapper; +import com.gxwebsoft.gxmu.service.CollegeResolver; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 学院标识解析实现:gxmu_student.college_id 是学院标识的唯一权威来源。 + * + *
口径与一次性回填脚本 sql/20261110_backfill_college_id.sql 保持一致:
+ * 优先取「在校(status = 1)」那条学籍的学院;学院不唯一就留空交给人工,
+ * 不做层级推导、不猜。
+ *
+ * @author Codex
+ * @since 2026-11-11
+ */
+@Service
+public class CollegeResolverImpl implements CollegeResolver {
+
+ @Resource
+ private GxmuStudentMapper gxmuStudentMapper;
+
+ @Override
+ public Integer resolve(Integer userId) {
+ if (userId == null) {
+ return null;
+ }
+ List 钉三件事:有学籍时新申报写入学院、没有学籍时留空不报错、更新路径不覆盖已有值。
+ * 不启 Spring、不连库。
+ *
+ * @author Codex
+ * @since 2026-11-11
+ */
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+class CollegeResolverImplTest {
+
+ private static final Integer USER_ID = 12;
+ private static final Integer COLLEGE_ID = 100;
+
+ @Mock
+ private GxmuStudentMapper gxmuStudentMapper;
+
+ @InjectMocks
+ private CollegeResolverImpl collegeResolver;
+
+ /** MyBatis-Plus 的 lambda 列名解析需要 TableInfo,纯单元测试里手工初始化一次 */
+ @BeforeAll
+ static void initLambdaCache() {
+ MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
+ TableInfoHelper.initTableInfo(assistant, GxmuStudent.class);
+ }
+
+ /** 有学籍:按 gxmu_student.college_id 解析出学院。 */
+ @Test
+ void resolveReturnsCollegeFromStudentRecord() {
+ when(gxmuStudentMapper.selectList(any()))
+ .thenReturn(Collections.singletonList(student(COLLEGE_ID, 1)));
+
+ assertEquals(COLLEGE_ID, collegeResolver.resolve(USER_ID));
+ }
+
+ /** 没有学籍(或学籍没学院):留空,不报错——申报不能因为名册没同步就提交不了。 */
+ @Test
+ void resolveReturnsNullWithoutStudentRecord() {
+ when(gxmuStudentMapper.selectList(any())).thenReturn(Collections.emptyList());
+
+ assertNull(collegeResolver.resolve(USER_ID));
+ assertNull(collegeResolver.resolve(null));
+ }
+
+ /** 同一用户本科学院与研究生学院不同且没有在校行:留空,不猜。 */
+ @Test
+ void resolveReturnsNullWhenCollegesAreAmbiguous() {
+ when(gxmuStudentMapper.selectList(any())).thenReturn(Arrays.asList(
+ student(COLLEGE_ID, 0),
+ student(200, 0)));
+
+ assertNull(collegeResolver.resolve(USER_ID));
+ }
+
+ /** 新建申报:没有学院标识时写入解析结果。 */
+ @Test
+ void fillIfAbsentWritesResolvedCollegeForNewRecord() {
+ when(gxmuStudentMapper.selectList(any()))
+ .thenReturn(Collections.singletonList(student(COLLEGE_ID, 1)));
+
+ assertEquals(COLLEGE_ID, collegeResolver.fillIfAbsent(null, USER_ID));
+ }
+
+ /** 更新路径:已有学院标识一律保留,不被登录用户的学籍覆盖。 */
+ @Test
+ void fillIfAbsentKeepsExistingCollegeOnUpdate() {
+ assertEquals(Integer.valueOf(999), collegeResolver.fillIfAbsent(999, USER_ID));
+ // 已有值时不查库
+ assertEquals(Integer.valueOf(999), collegeResolver.fillIfAbsent(999, null));
+ }
+
+ private GxmuStudent student(Integer collegeId, Integer status) {
+ GxmuStudent student = new GxmuStudent();
+ student.setUserId(USER_ID);
+ student.setCollegeId(collegeId);
+ student.setStatus(status);
+ return student;
+ }
+}