diff --git a/docs/sql/2026-07-30-school-default-task-initialization.sql b/docs/sql/2026-07-30-school-default-task-initialization.sql new file mode 100644 index 0000000..ef111f7 --- /dev/null +++ b/docs/sql/2026-07-30-school-default-task-initialization.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS school_default_task_initialization ( + school_id varchar(64) NOT NULL, + status varchar(16) NOT NULL, + last_error varchar(1000) NULL, + attempt_count int NOT NULL DEFAULT 0, + create_time datetime NOT NULL, + update_time datetime NOT NULL, + PRIMARY KEY (school_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学校默认实训任务初始化状态'; diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/SchoolDefaultTaskInitialization.java b/src/main/java/com/sztzjy/linkCommerce/entity/SchoolDefaultTaskInitialization.java new file mode 100644 index 0000000..f30656e --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/entity/SchoolDefaultTaskInitialization.java @@ -0,0 +1,15 @@ +package com.sztzjy.linkCommerce.entity; + +import lombok.Data; + +import java.util.Date; + +@Data +public class SchoolDefaultTaskInitialization { + private String schoolId; + private String status; + private String lastError; + private Integer attemptCount; + private Date createTime; + private Date updateTime; +} diff --git a/src/main/java/com/sztzjy/linkCommerce/mapper/SchoolDefaultTaskInitializationMapper.java b/src/main/java/com/sztzjy/linkCommerce/mapper/SchoolDefaultTaskInitializationMapper.java new file mode 100644 index 0000000..7221b77 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/mapper/SchoolDefaultTaskInitializationMapper.java @@ -0,0 +1,13 @@ +package com.sztzjy.linkCommerce.mapper; + +import com.sztzjy.linkCommerce.entity.SchoolDefaultTaskInitialization; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SchoolDefaultTaskInitializationMapper { + SchoolDefaultTaskInitialization selectByPrimaryKey(String schoolId); + + int insert(SchoolDefaultTaskInitialization record); + + int updateByPrimaryKeySelective(SchoolDefaultTaskInitialization record); +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/SchoolDefaultTaskService.java b/src/main/java/com/sztzjy/linkCommerce/service/SchoolDefaultTaskService.java new file mode 100644 index 0000000..247d344 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/SchoolDefaultTaskService.java @@ -0,0 +1,20 @@ +package com.sztzjy.linkCommerce.service; + +import com.sztzjy.linkCommerce.entity.SchoolDefaultTaskInitialization; +import com.sztzjy.linkCommerce.entity.TaskAllocation; + +import java.util.List; + +public interface SchoolDefaultTaskService { + String PLATFORM_DEFAULT_CLASS_ID = "999999999"; + + List resolveForTeachingClass(String teachingClassId); + + List getSchoolDefault(String schoolId); + + SchoolDefaultTaskInitialization getInitialization(String schoolId); + + void replaceSchoolDefault(String schoolId, List tasks); + + void initializeSchoolDefault(String schoolId); +} diff --git a/src/main/java/com/sztzjy/linkCommerce/service/impl/SchoolDefaultTaskServiceImpl.java b/src/main/java/com/sztzjy/linkCommerce/service/impl/SchoolDefaultTaskServiceImpl.java new file mode 100644 index 0000000..11875e2 --- /dev/null +++ b/src/main/java/com/sztzjy/linkCommerce/service/impl/SchoolDefaultTaskServiceImpl.java @@ -0,0 +1,128 @@ +package com.sztzjy.linkCommerce.service.impl; + +import com.sztzjy.linkCommerce.entity.SchoolClass; +import com.sztzjy.linkCommerce.entity.SchoolDefaultTaskInitialization; +import com.sztzjy.linkCommerce.entity.TaskAllocation; +import com.sztzjy.linkCommerce.entity.TaskAllocationExample; +import com.sztzjy.linkCommerce.mapper.SchoolClassMapper; +import com.sztzjy.linkCommerce.mapper.SchoolDefaultTaskInitializationMapper; +import com.sztzjy.linkCommerce.mapper.TaskAllocationMapper; +import com.sztzjy.linkCommerce.service.SchoolDefaultTaskService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +@Service +public class SchoolDefaultTaskServiceImpl implements SchoolDefaultTaskService { + private static final String SCHOOL_DEFAULT_PREFIX = "SCHOOL_DEFAULT:"; + + @Autowired + TaskAllocationMapper taskAllocationMapper; + @Autowired + SchoolClassMapper schoolClassMapper; + @Autowired(required = false) + SchoolDefaultTaskInitializationMapper initializationMapper; + + public static String schoolDefaultClassId(String schoolId) { + return SCHOOL_DEFAULT_PREFIX + schoolId; + } + + @Override + public List resolveForTeachingClass(String teachingClassId) { + List teachingClassTasks = listByClassId(teachingClassId); + if (!teachingClassTasks.isEmpty()) { + return teachingClassTasks; + } + SchoolClass schoolClass = schoolClassMapper == null ? null : schoolClassMapper.selectByPrimaryKey(teachingClassId); + if (schoolClass != null && StringUtils.isNotBlank(schoolClass.getSchoolId())) { + List schoolTasks = getSchoolDefault(schoolClass.getSchoolId()); + if (!schoolTasks.isEmpty()) { + return schoolTasks; + } + } + return listByClassId(PLATFORM_DEFAULT_CLASS_ID); + } + + @Override + public List getSchoolDefault(String schoolId) { + if (StringUtils.isBlank(schoolId)) { + return Collections.emptyList(); + } + return listByClassId(schoolDefaultClassId(schoolId)); + } + + @Override + public SchoolDefaultTaskInitialization getInitialization(String schoolId) { + return initializationMapper == null || StringUtils.isBlank(schoolId) + ? null : initializationMapper.selectByPrimaryKey(schoolId); + } + + @Override + public void replaceSchoolDefault(String schoolId, List tasks) { + if (StringUtils.isBlank(schoolId)) { + throw new IllegalArgumentException("学校不能为空"); + } + String classId = schoolDefaultClassId(schoolId); + TaskAllocationExample example = new TaskAllocationExample(); + example.createCriteria().andClassIdEqualTo(classId); + taskAllocationMapper.deleteByExample(example); + for (TaskAllocation task : tasks == null ? Collections.emptyList() : tasks) { + task.setId(UUID.randomUUID().toString()); + task.setClassId(classId); + task.setSchoolId(schoolId); + taskAllocationMapper.insert(task); + } + } + + @Override + public void initializeSchoolDefault(String schoolId) { + if (StringUtils.isBlank(schoolId) || hasSchoolDefault(schoolId)) { + return; + } + List platformTasks = listByClassId(PLATFORM_DEFAULT_CLASS_ID); + for (TaskAllocation source : platformTasks) { + TaskAllocation copy = new TaskAllocation(); + copy.setId(UUID.randomUUID().toString()); + copy.setClassId(schoolDefaultClassId(schoolId)); + copy.setSchoolId(schoolId); + copy.setModule(source.getModule()); + copy.setDisabledStatus(source.getDisabledStatus()); + copy.setSort(source.getSort()); + taskAllocationMapper.insert(copy); + } + markReady(schoolId); + } + + private boolean hasSchoolDefault(String schoolId) { + TaskAllocationExample example = new TaskAllocationExample(); + example.createCriteria().andClassIdEqualTo(schoolDefaultClassId(schoolId)); + return taskAllocationMapper.countByExample(example) > 0; + } + + private List listByClassId(String classId) { + if (taskAllocationMapper == null || StringUtils.isBlank(classId)) { + return Collections.emptyList(); + } + TaskAllocationExample example = new TaskAllocationExample(); + example.createCriteria().andClassIdEqualTo(classId); + example.setOrderByClause("sort asc"); + List tasks = taskAllocationMapper.selectByExample(example); + return tasks == null ? Collections.emptyList() : tasks; + } + + private void markReady(String schoolId) { + if (initializationMapper == null) { + return; + } + SchoolDefaultTaskInitialization record = new SchoolDefaultTaskInitialization(); + record.setSchoolId(schoolId); + record.setStatus("READY"); + record.setUpdateTime(new Date()); + initializationMapper.updateByPrimaryKeySelective(record); + } +} diff --git a/src/main/resources/mappers/SchoolDefaultTaskInitializationMapper.xml b/src/main/resources/mappers/SchoolDefaultTaskInitializationMapper.xml new file mode 100644 index 0000000..8ccc6e5 --- /dev/null +++ b/src/main/resources/mappers/SchoolDefaultTaskInitializationMapper.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + school_id, status, last_error, attempt_count, create_time, update_time + + + INSERT INTO school_default_task_initialization () + VALUES (#{schoolId}, #{status}, #{lastError}, #{attemptCount}, #{createTime}, #{updateTime}) + + + UPDATE school_default_task_initialization + + status = #{status}, + last_error = #{lastError}, + attempt_count = #{attemptCount}, + update_time = #{updateTime}, + + WHERE school_id = #{schoolId} + + diff --git a/src/test/java/com/sztzjy/linkCommerce/service/impl/SchoolDefaultTaskServiceImplTest.java b/src/test/java/com/sztzjy/linkCommerce/service/impl/SchoolDefaultTaskServiceImplTest.java new file mode 100644 index 0000000..eb425fc --- /dev/null +++ b/src/test/java/com/sztzjy/linkCommerce/service/impl/SchoolDefaultTaskServiceImplTest.java @@ -0,0 +1,59 @@ +package com.sztzjy.linkCommerce.service.impl; + +import com.sztzjy.linkCommerce.entity.SchoolClass; +import com.sztzjy.linkCommerce.entity.TaskAllocation; +import com.sztzjy.linkCommerce.mapper.SchoolClassMapper; +import com.sztzjy.linkCommerce.mapper.TaskAllocationMapper; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +class SchoolDefaultTaskServiceImplTest { + + @Test + void resolvesSchoolDefaultBeforePlatformDefaultWhenTeachingClassHasNoConfiguration() { + TaskAllocationMapper taskMapper = mock(TaskAllocationMapper.class); + SchoolClassMapper classMapper = mock(SchoolClassMapper.class); + SchoolDefaultTaskServiceImpl service = new SchoolDefaultTaskServiceImpl(); + ReflectionTestUtils.setField(service, "taskAllocationMapper", taskMapper); + ReflectionTestUtils.setField(service, "schoolClassMapper", classMapper); + + SchoolClass schoolClass = new SchoolClass(); + schoolClass.setSchoolClassId("class-1"); + schoolClass.setSchoolId("school-1"); + schoolClass.setClassType("TEACHING"); + when(classMapper.selectByPrimaryKey("class-1")).thenReturn(schoolClass); + when(taskMapper.selectByExample(any())).thenReturn( + Collections.emptyList(), List.of(task("学校默认任务")), List.of(task("平台默认任务"))); + + List result = service.resolveForTeachingClass("class-1"); + + assertEquals(List.of("学校默认任务"), result.stream().map(TaskAllocation::getModule).collect(java.util.stream.Collectors.toList())); + verify(taskMapper, never()).insert(any()); + } + + @Test + void initializationDoesNotOverwriteExistingSchoolDefaultTasks() { + TaskAllocationMapper taskMapper = mock(TaskAllocationMapper.class); + SchoolDefaultTaskServiceImpl service = new SchoolDefaultTaskServiceImpl(); + ReflectionTestUtils.setField(service, "taskAllocationMapper", taskMapper); + when(taskMapper.countByExample(any())).thenReturn(1L); + + service.initializeSchoolDefault("school-1"); + + verify(taskMapper, never()).insert(any()); + } + + private TaskAllocation task(String module) { + TaskAllocation task = new TaskAllocation(); + task.setModule(module); + task.setDisabledStatus((byte) 0); + return task; + } +}