diff --git a/src/main/java/com/lion/lionwebsite/Dao/normal/CustomConfigurationMapper.java b/src/main/java/com/lion/lionwebsite/Dao/normal/CustomConfigurationMapper.java index 380e0a7..361875e 100644 --- a/src/main/java/com/lion/lionwebsite/Dao/normal/CustomConfigurationMapper.java +++ b/src/main/java/com/lion/lionwebsite/Dao/normal/CustomConfigurationMapper.java @@ -16,6 +16,9 @@ public interface CustomConfigurationMapper { // @Delete("delete from customConfiguration where parameter=#{parameter}") // void deleteConfiguration(CustomConfiguration configuration); + @Update("update customConfiguration set value=cast(value as integer)+#{amount} where parameter=#{parameter}") + void incrementConfiguration(@Param("parameter") String parameter, @Param("amount") long amount); + @Select("select * from customConfiguration where parameter=#{parameter}") CustomConfiguration selectConfiguration(String parameter); } diff --git a/src/main/java/com/lion/lionwebsite/Service/GalleryManageService.java b/src/main/java/com/lion/lionwebsite/Service/GalleryManageService.java index 49a60d6..06e39f0 100644 --- a/src/main/java/com/lion/lionwebsite/Service/GalleryManageService.java +++ b/src/main/java/com/lion/lionwebsite/Service/GalleryManageService.java @@ -93,9 +93,14 @@ public class GalleryManageService { } else { taskName = gallery.getName(); log.info("创建任务: {} 目标分辨率:{}", link, targetResolution); + // Persist before dispatch: the node sends its current status before its ACK. + gallery.setDownloader(user.getId()); + gallery.set_download(true); + galleryMapper.insertGallery(gallery); + configurationMapper.incrementConfiguration(CustomConfiguration.WEEK_USED_AMOUNT, gallery.getFileSize()); if (remoteService.addGalleryToQueue(gallery) != 0) { log.error("传送任务{}失败, 未知原因", gallery.getName()); - response.failure("任务传送失败,未知原因,尝试点击重连按钮看看"); + response.failure("任务已保存,但节点未确认接收;请刷新任务列表后重试"); pushService.taskCreateReport(user.getUsername(), taskName, response); return response.toJSONString(); } @@ -111,20 +116,9 @@ public class GalleryManageService { return response.toJSONString(); } - //处理下载结果,将任务插入数据库并且更新每周用量 - if (gallery.getStatus().equals("已提交")) { - response.success(gallery.toString()); - gallery.setDownloader(user.getId()); - gallery.set_download(true); - galleryMapper.insertGallery(gallery); - - long usedAmount = Long.parseLong(configurationMapper.selectConfiguration(CustomConfiguration.WEEK_USED_AMOUNT).getValue()); - usedAmount += gallery.getFileSize(); - configurationMapper.updateConfiguration(CustomConfiguration.WEEK_USED_AMOUNT, String.valueOf(usedAmount)); - } else { - response.failure("提交失败,未知原因"); - galleryMapper.deleteGalleryByGid(gallery.getGid()); - } + // Do not overwrite an immediate node status with the original submitted state. + Gallery current = galleryMapper.selectGalleryByGid(gallery.getGid()); + response.success((current == null ? gallery : current).toString()); pushService.taskCreateReport(user.getUsername(), taskName, response); return response.toJSONString(); diff --git a/src/test/java/com/lion/lionwebsite/Service/GallerySubmissionTest.java b/src/test/java/com/lion/lionwebsite/Service/GallerySubmissionTest.java new file mode 100644 index 0000000..0e5c49a --- /dev/null +++ b/src/test/java/com/lion/lionwebsite/Service/GallerySubmissionTest.java @@ -0,0 +1,57 @@ +package com.lion.lionwebsite.Service; + +import com.lion.lionwebsite.Dao.normal.*; +import com.lion.lionwebsite.Dao.cache.ImageCacheMapper; +import com.lion.lionwebsite.Domain.*; +import com.lion.lionwebsite.Util.GalleryUtil; +import org.junit.jupiter.api.Test; +import java.util.concurrent.atomic.AtomicReference; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class GallerySubmissionTest { + @Test + void immediateCompletionSeesPersistedTask() throws Exception { + checkSubmission((byte) 0); + } + + @Test + void timeoutRetainsTaskForRetry() throws Exception { + checkSubmission((byte) -1); + } + + private void checkSubmission(byte ack) throws Exception { + GalleryMapper galleries = mock(GalleryMapper.class); + UserMapper users = mock(UserMapper.class); + RemoteService remote = mock(RemoteService.class); + CustomConfigurationMapper configuration = mock(CustomConfigurationMapper.class); + GalleryManageService service = new GalleryManageService(galleries, mock(CollectMapper.class), configuration, + users, mock(ShareFileMapper.class), mock(ImageCacheMapper.class), remote, mock(PushService.class)); + User user = new User(); + user.setId(7); + user.setUsername("test"); + when(users.selectUserByAuthCode("test")).thenReturn(user); + AtomicReference saved = new AtomicReference<>(); + when(galleries.selectGalleryByGid(123)).thenAnswer(call -> saved.get()); + doAnswer(call -> { saved.set(call.getArgument(0)); return null; }).when(galleries).insertGallery(any()); + Gallery gallery = new Gallery(); + gallery.setGid(123); + gallery.setName("sample [123]"); + gallery.setStatus("已提交"); + when(remote.addGalleryToQueue(any())).thenAnswer(call -> { + assertNotNull(saved.get(), "node status must find a persisted record"); + assertEquals(7, saved.get().getDownloader()); + if (ack == 0) saved.get().setStatus("下载完成"); + return ack; + }); + try (var parser = mockStatic(GalleryUtil.class)) { + parser.when(() -> GalleryUtil.parse("https://example.org/g/123/key/", true, "original")).thenReturn(gallery); + String response = service.createTask("https://example.org/g/123/key/", "original", "test"); + assertNotNull(saved.get()); + assertTrue(response.contains(ack == 0 ? "success" : "任务已保存")); + assertEquals(ack == 0 ? "下载完成" : "已提交", saved.get().getStatus()); + verify(galleries, never()).deleteGalleryByGid(any()); + verify(galleries, times(1)).insertGallery(gallery); + } + } +}