From da793e9881239a3e9072329bca90c8cd5e62d098 Mon Sep 17 00:00:00 2001 From: Mikira Sora Date: Sat, 17 Dec 2022 11:50:36 +0000 Subject: [PATCH] [ongeki,api] support ongeki rival --- config/application.properties | 2 + .../ongeki/ApiOngekiPlayerDataController.java | 58 ++++++++++++++++- .../aqua/api/model/ObjectMessageResponse.java | 29 +++++++++ .../ongeki/external/OngekiDataExport.java | 2 + .../ongeki/external/OngekiDataImport.java | 2 + .../ongeki/controller/OngekiController.java | 8 +-- .../dao/userdata/UserDataRepository.java | 3 + .../dao/userdata/UserRivalDataRepository.java | 24 +++++++ .../handler/impl/GetUserRivalDataHandler.java | 63 +++++++++++++++++++ .../handler/impl/GetUserRivalHandler.java | 11 +++- .../impl/GetUserRivalMusicHandler.java | 58 +++++++++++++---- .../model/response/data/GameSetting.java | 2 +- .../model/response/data/UserRivalData.java | 16 +++++ .../model/response/data/UserRivalMusic.java | 19 ++++++ .../sega/ongeki/model/userdata/UserRival.java | 38 +++++++++++ .../sega/util/jackson/UserIdSerializer.java | 27 ++++++++ .../mariadb/V222__add_ongeki_user_rival.sql | 32 ++++++++++ .../mysql/V222__add_ongeki_user_rival.sql | 32 ++++++++++ .../sqlite/V222__add_ongeki_user_rival.sql | 9 +++ .../dao/userdata/OngekiRepositoryTest.java | 32 ++++++++++ .../icu/samnyan/aqua/util/CardHelper.java | 15 +++++ 21 files changed, 461 insertions(+), 21 deletions(-) create mode 100644 src/main/java/icu/samnyan/aqua/api/model/ObjectMessageResponse.java create mode 100644 src/main/java/icu/samnyan/aqua/sega/ongeki/dao/userdata/UserRivalDataRepository.java create mode 100644 src/main/java/icu/samnyan/aqua/sega/ongeki/handler/impl/GetUserRivalDataHandler.java create mode 100644 src/main/java/icu/samnyan/aqua/sega/ongeki/model/response/data/UserRivalData.java create mode 100644 src/main/java/icu/samnyan/aqua/sega/ongeki/model/response/data/UserRivalMusic.java create mode 100644 src/main/java/icu/samnyan/aqua/sega/ongeki/model/userdata/UserRival.java create mode 100644 src/main/java/icu/samnyan/aqua/sega/util/jackson/UserIdSerializer.java create mode 100644 src/main/resources/db/migration/mariadb/V222__add_ongeki_user_rival.sql create mode 100644 src/main/resources/db/migration/mysql/V222__add_ongeki_user_rival.sql create mode 100644 src/main/resources/db/migration/sqlite/V222__add_ongeki_user_rival.sql diff --git a/config/application.properties b/config/application.properties index b66623ec..ae23c08c 100644 --- a/config/application.properties +++ b/config/application.properties @@ -45,6 +45,8 @@ game.chusan.team-name= ## The version of your client. Match this with DataConfig.xml file in latest option. (only if bright memory and up) ## For example, if DataConfig.xml says "1, 35, 1" then this need to be 1.35.01 game.ongeki.version=1.05.00 +## Limit rival list size for per user, it will be unlimited if this value <= 0 (CAUTION!). +game.ongeki.rival.rivals-max-count=10 ## Maimai DX ## Set this true if you are using old version of Splash network patch and have no other choice. diff --git a/src/main/java/icu/samnyan/aqua/api/controller/sega/game/ongeki/ApiOngekiPlayerDataController.java b/src/main/java/icu/samnyan/aqua/api/controller/sega/game/ongeki/ApiOngekiPlayerDataController.java index d43a708b..c0d1bad5 100644 --- a/src/main/java/icu/samnyan/aqua/api/controller/sega/game/ongeki/ApiOngekiPlayerDataController.java +++ b/src/main/java/icu/samnyan/aqua/api/controller/sega/game/ongeki/ApiOngekiPlayerDataController.java @@ -2,6 +2,7 @@ package icu.samnyan.aqua.api.controller.sega.game.ongeki; import com.fasterxml.jackson.core.type.TypeReference; import icu.samnyan.aqua.api.model.MessageResponse; +import icu.samnyan.aqua.api.model.ObjectMessageResponse; import icu.samnyan.aqua.api.model.ReducedPageResponse; import icu.samnyan.aqua.api.model.resp.sega.ongeki.ProfileResp; import icu.samnyan.aqua.api.model.resp.sega.ongeki.external.ExternalUserData; @@ -13,7 +14,9 @@ import icu.samnyan.aqua.sega.general.service.CardService; import icu.samnyan.aqua.sega.ongeki.dao.gamedata.GameCardRepository; import icu.samnyan.aqua.sega.ongeki.dao.userdata.*; import icu.samnyan.aqua.sega.ongeki.model.gamedata.GameCard; +import icu.samnyan.aqua.sega.ongeki.model.response.data.UserRivalData; import icu.samnyan.aqua.sega.ongeki.model.userdata.*; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; @@ -61,6 +64,7 @@ public class ApiOngekiPlayerDataController { private final UserEventMusicRepository userEventMusicRepository; private final UserTechEventRepository userTechEventRepository; private final UserKopRepository userKopRepository; + private final UserRivalDataRepository userRivalDataRepository; private final UserMemoryChapterRepository userMemoryChapterRepository; @@ -72,7 +76,7 @@ public class ApiOngekiPlayerDataController { private final GameCardRepository gameCardRepository; - public ApiOngekiPlayerDataController(ApiMapper mapper, CardService cardService, UserActivityRepository userActivityRepository, UserCardRepository userCardRepository, UserChapterRepository userChapterRepository, UserCharacterRepository userCharacterRepository, UserDataRepository userDataRepository, UserDeckRepository userDeckRepository, UserEventPointRepository userEventPointRepository, UserItemRepository userItemRepository, UserLoginBonusRepository userLoginBonusRepository, UserMissionPointRepository userMissionPointRepository, UserMusicDetailRepository userMusicDetailRepository, UserMusicItemRepository userMusicItemRepository, UserOptionRepository userOptionRepository, UserPlaylogRepository userPlaylogRepository, UserStoryRepository userStoryRepository, UserTrainingRoomRepository userTrainingRoomRepository, UserGeneralDataRepository userGeneralDataRepository, GameCardRepository gameCardRepository, UserTradeItemRepository userTradeItemRepository, UserEventMusicRepository userEventMusicRepository, UserTechEventRepository userTechEventRepository, UserKopRepository userKopRepository, UserMemoryChapterRepository userMemoryChapterRepository, UserScenarioRepository userScenarioRepository, UserBossRepository userBossRepository, UserTechCountRepository userTechCountRepository) { + public ApiOngekiPlayerDataController(ApiMapper mapper, CardService cardService, UserRivalDataRepository userRivalDataRepository, UserActivityRepository userActivityRepository, UserCardRepository userCardRepository, UserChapterRepository userChapterRepository, UserCharacterRepository userCharacterRepository, UserDataRepository userDataRepository, UserDeckRepository userDeckRepository, UserEventPointRepository userEventPointRepository, UserItemRepository userItemRepository, UserLoginBonusRepository userLoginBonusRepository, UserMissionPointRepository userMissionPointRepository, UserMusicDetailRepository userMusicDetailRepository, UserMusicItemRepository userMusicItemRepository, UserOptionRepository userOptionRepository, UserPlaylogRepository userPlaylogRepository, UserStoryRepository userStoryRepository, UserTrainingRoomRepository userTrainingRoomRepository, UserGeneralDataRepository userGeneralDataRepository, GameCardRepository gameCardRepository, UserTradeItemRepository userTradeItemRepository, UserEventMusicRepository userEventMusicRepository, UserTechEventRepository userTechEventRepository, UserKopRepository userKopRepository, UserMemoryChapterRepository userMemoryChapterRepository, UserScenarioRepository userScenarioRepository, UserBossRepository userBossRepository, UserTechCountRepository userTechCountRepository) { this.mapper = mapper; this.cardService = cardService; this.userActivityRepository = userActivityRepository; @@ -101,6 +105,7 @@ public class ApiOngekiPlayerDataController { this.userScenarioRepository = userScenarioRepository; this.userBossRepository = userBossRepository; this.userTechCountRepository = userTechCountRepository; + this.userRivalDataRepository = userRivalDataRepository; } @GetMapping("profile") @@ -334,6 +339,51 @@ public class ApiOngekiPlayerDataController { return userPlaylogRepository.findByUser_Card_ExtIdAndMusicIdAndLevel(aimeId, id, level); } + @GetMapping("rival") + public List getRival(@RequestParam long aimeId) { + var rivalUserIds = userRivalDataRepository.findByUser_Card_ExtId(aimeId) + .stream() + .map(x -> x.getRivalUserId()) + .collect(Collectors.toList()); + + var rivalDataList = userDataRepository.findByCard_ExtIdIn(rivalUserIds) + .stream() + .map(x -> new UserRivalData(x.getCard().getExtId().longValue(), x.getUserName())) + .collect(Collectors.toList()); + + return rivalDataList; + } + + @DeleteMapping("rival") + public MessageResponse deleteRival(@RequestParam long aimeId, @RequestParam long rivalAimeId) { + userRivalDataRepository.removeByUser_Card_ExtIdAndRivalUserId(aimeId, rivalAimeId); + return new MessageResponse(); + } + + @PostMapping("rival") + public ObjectMessageResponse addRival(@RequestParam long aimeId, @RequestParam long rivalAimeId, @Value("${game.ongeki.rival.rivals-max-count:10}") long addMaxCount) { + //check limit + if (addMaxCount >= 0 && userRivalDataRepository.findByUser_Card_ExtId(aimeId).size() >= addMaxCount) { + return new ObjectMessageResponse<>(String.format("Size of rival list is limited in %d", addMaxCount)); + } + + var userOpt = userDataRepository.findByCard_ExtId(aimeId); + if (userOpt.isEmpty()) + return new ObjectMessageResponse<>("Current user isn't ongeki player."); + var user = userOpt.get(); + var rivalUserOpt = userDataRepository.findByCard_ExtId(rivalAimeId); + if (rivalUserOpt.isEmpty()) + return new ObjectMessageResponse<>("Rival user isn't ongeki player."); + var rivalUser = rivalUserOpt.get(); + + var rival = new UserRival(); + rival.setUser(user); + rival.setRivalUserId(rivalUser.getCard().getExtId()); + + userRivalDataRepository.save(rival); + return new ObjectMessageResponse<>(new UserRivalData(rivalUser.getCard().getExtId(), rivalUser.getUserName())); + } + @GetMapping("options") public UserOption getOptions(@RequestParam long aimeId) { return userOptionRepository.findByUser_Card_ExtId(aimeId).orElseThrow(); @@ -376,6 +426,7 @@ public class ApiOngekiPlayerDataController { data.setUserScenarioList(userScenarioRepository.findByUser_Card_ExtId(aimeId)); data.setUserBossList(userBossRepository.findByUser_Card_ExtId(aimeId)); data.setUserTechCountList(userTechCountRepository.findByUser_Card_ExtId(aimeId)); + data.setUserRivalList(userRivalDataRepository.findByUser_Card_ExtId(aimeId)); } catch (NoSuchElementException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(new MessageResponse("User not found")); @@ -454,6 +505,8 @@ public class ApiOngekiPlayerDataController { userBossRepository.flush(); userTechCountRepository.deleteByUser(existUserData.get()); userTechCountRepository.flush(); + userRivalDataRepository.deleteByUser(existUserData.get()); + userRivalDataRepository.flush(); userDataRepository.deleteByCard(card); userDataRepository.flush(); @@ -539,6 +592,9 @@ public class ApiOngekiPlayerDataController { userTechCountRepository.saveAll(Optional.ofNullable(data.getUserTechCountList()).orElse(Collections.emptyList()).stream() .peek(x -> x.setUser(userData)).collect(Collectors.toList())); + userRivalDataRepository.saveAll(Optional.ofNullable(data.getUserRivalList()).orElse(Collections.emptyList()).stream() + .peek(x -> x.setUser(userData)).collect(Collectors.toList())); + return ResponseEntity.ok(new MessageResponse("Import successfully, aimeId: " + card.getExtId())); } diff --git a/src/main/java/icu/samnyan/aqua/api/model/ObjectMessageResponse.java b/src/main/java/icu/samnyan/aqua/api/model/ObjectMessageResponse.java new file mode 100644 index 00000000..888fba48 --- /dev/null +++ b/src/main/java/icu/samnyan/aqua/api/model/ObjectMessageResponse.java @@ -0,0 +1,29 @@ +package icu.samnyan.aqua.api.model; + +import icu.samnyan.aqua.api.model.MessageResponse; + +public class ObjectMessageResponse extends MessageResponse { + private T data; + + public ObjectMessageResponse(String message) { + super(message); + } + + public ObjectMessageResponse(T data) { + super(); + setData(data); + } + + public ObjectMessageResponse(T data, String message) { + super(message); + setData(data); + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } +} diff --git a/src/main/java/icu/samnyan/aqua/api/model/resp/sega/ongeki/external/OngekiDataExport.java b/src/main/java/icu/samnyan/aqua/api/model/resp/sega/ongeki/external/OngekiDataExport.java index 1436bda6..a83516cb 100644 --- a/src/main/java/icu/samnyan/aqua/api/model/resp/sega/ongeki/external/OngekiDataExport.java +++ b/src/main/java/icu/samnyan/aqua/api/model/resp/sega/ongeki/external/OngekiDataExport.java @@ -6,6 +6,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; +import java.util.Map; /** * @author samnyan (privateamusement@protonmail.com) @@ -40,4 +41,5 @@ public class OngekiDataExport { private List userScenarioList; private List userBossList; private List userTechCountList; + private List userRivalList; } diff --git a/src/main/java/icu/samnyan/aqua/api/model/resp/sega/ongeki/external/OngekiDataImport.java b/src/main/java/icu/samnyan/aqua/api/model/resp/sega/ongeki/external/OngekiDataImport.java index f1dcef5d..0e1ffc39 100644 --- a/src/main/java/icu/samnyan/aqua/api/model/resp/sega/ongeki/external/OngekiDataImport.java +++ b/src/main/java/icu/samnyan/aqua/api/model/resp/sega/ongeki/external/OngekiDataImport.java @@ -6,6 +6,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; +import java.util.Map; /** * @author samnyan (privateamusement@protonmail.com) @@ -40,4 +41,5 @@ public class OngekiDataImport { private List userScenarioList; private List userBossList; private List userTechCountList; + private List userRivalList; } diff --git a/src/main/java/icu/samnyan/aqua/sega/ongeki/controller/OngekiController.java b/src/main/java/icu/samnyan/aqua/sega/ongeki/controller/OngekiController.java index 5d5d0d55..bcaa66ee 100644 --- a/src/main/java/icu/samnyan/aqua/sega/ongeki/controller/OngekiController.java +++ b/src/main/java/icu/samnyan/aqua/sega/ongeki/controller/OngekiController.java @@ -51,6 +51,7 @@ public class OngekiController { private final GetUserRegionHandler getUserRegionHandler; private final GetUserRivalHandler getUserRivalHandler; private final GetUserRivalMusicHandler getUserRivalMusicHandler; + private final GetUserRivalDataHandler getUserRivalDataHandler; private final GetUserScenarioHandler getUserScenarioHandler; private final GetUserStoryHandler getUserStoryHandler; private final GetUserTechCountHandler getUserTechCountHandler; @@ -65,7 +66,7 @@ public class OngekiController { private final GetGameMusicReleaseStateHandler getGameMusicReleaseStateHandler; @Autowired - public OngekiController(GetGameEventHandler getGameEventHandler, GetGameIdlistHandler getGameIdlistHandler, GetGameMessageHandler getGameMessageHandler, GetGamePointHandler getGamePointHandler, GetGamePresentHandler getGamePresentHandler, GetGameRankingHandler getGameRankingHandler, GetGameRewardHandler getGameRewardHandler, GetGameSettingHandler getGameSettingHandler, GetUserActivityHandler getUserActivityHandler, GetUserBossHandler getUserBossHandler, GetUserBpBaseHandler getUserBpBaseHandler, GetUserCardHandler getUserCardHandler, GetUserChapterHandler getUserChapterHandler, GetUserCharacterHandler getUserCharacterHandler, GetUserDataHandler getUserDataHandler, GetUserDeckByKeyHandler getUserDeckByKeyHandler, GetUserEventPointHandler getUserEventPointHandler, GetUserEventRankingHandler getUserEventRankingHandler, GetUserItemHandler getUserItemHandler, GetUserLoginBonusHandler getUserLoginBonusHandler, GetUserMissionPointHandler getUserMissionPointHandler, GetUserMusicHandler getUserMusicHandler, GetUserMusicItemHandler getUserMusicItemHandler, GetUserOptionHandler getUserOptionHandler, GetUserPreviewHandler getUserPreviewHandler, GetUserRatinglogListHandler getUserRatinglogListHandler, GetUserRecentRatingHandler getUserRecentRatingHandler, GetUserRegionHandler getUserRegionHandler, GetUserRivalHandler getUserRivalHandler, GetUserRivalMusicHandler getUserRivalMusicHandler, GetUserScenarioHandler getUserScenarioHandler, GetUserStoryHandler getUserStoryHandler, GetUserTechCountHandler getUserTechCountHandler, GetUserTrainingRoomByKeyHandler getUserTrainingRoomByKeyHandler, UpsertUserAllHandler upsertUserAllHandler, GetGameTechMusicHandler getGameTechMusicHandler, GetUserTechEventHandler getUserTechEventHandler, GetUserTechEventRankingHandler getUserTechEventRankingHandler, GetUserEventMusicHandler getUserEventMusicHandler, GetUserTradeItemHandler getUserTradeItemHandler, GetUserKopHandler getUserKopHandler, GetClientBookkeepingHandler getClientBookkeepingHandler, GetClientTestmodeHandler getClientTestmodeHandler, GetGameMusicReleaseStateHandler getGameMusicReleaseStateHandler, GetUserMemoryChapterHandler getUserMemoryChapterHandler) { + public OngekiController(GetGameEventHandler getGameEventHandler, GetGameIdlistHandler getGameIdlistHandler, GetGameMessageHandler getGameMessageHandler, GetGamePointHandler getGamePointHandler, GetGamePresentHandler getGamePresentHandler, GetGameRankingHandler getGameRankingHandler, GetGameRewardHandler getGameRewardHandler, GetGameSettingHandler getGameSettingHandler, GetUserActivityHandler getUserActivityHandler, GetUserBossHandler getUserBossHandler, GetUserBpBaseHandler getUserBpBaseHandler, GetUserCardHandler getUserCardHandler, GetUserChapterHandler getUserChapterHandler, GetUserCharacterHandler getUserCharacterHandler, GetUserDataHandler getUserDataHandler, GetUserDeckByKeyHandler getUserDeckByKeyHandler, GetUserEventPointHandler getUserEventPointHandler, GetUserEventRankingHandler getUserEventRankingHandler, GetUserItemHandler getUserItemHandler, GetUserLoginBonusHandler getUserLoginBonusHandler, GetUserMissionPointHandler getUserMissionPointHandler, GetUserMusicHandler getUserMusicHandler, GetUserMusicItemHandler getUserMusicItemHandler, GetUserOptionHandler getUserOptionHandler, GetUserPreviewHandler getUserPreviewHandler, GetUserRatinglogListHandler getUserRatinglogListHandler, GetUserRecentRatingHandler getUserRecentRatingHandler, GetUserRegionHandler getUserRegionHandler, GetUserRivalHandler getUserRivalHandler, GetUserRivalMusicHandler getUserRivalMusicHandler, GetUserScenarioHandler getUserScenarioHandler, GetUserStoryHandler getUserStoryHandler, GetUserTechCountHandler getUserTechCountHandler, GetUserTrainingRoomByKeyHandler getUserTrainingRoomByKeyHandler, UpsertUserAllHandler upsertUserAllHandler, GetGameTechMusicHandler getGameTechMusicHandler, GetUserTechEventHandler getUserTechEventHandler, GetUserTechEventRankingHandler getUserTechEventRankingHandler, GetUserEventMusicHandler getUserEventMusicHandler, GetUserTradeItemHandler getUserTradeItemHandler, GetUserKopHandler getUserKopHandler, GetClientBookkeepingHandler getClientBookkeepingHandler, GetClientTestmodeHandler getClientTestmodeHandler, GetGameMusicReleaseStateHandler getGameMusicReleaseStateHandler, GetUserMemoryChapterHandler getUserMemoryChapterHandler, GetUserRivalDataHandler getUserRivalDataHandler) { this.getGameEventHandler = getGameEventHandler; this.getGameIdlistHandler = getGameIdlistHandler; this.getGameMessageHandler = getGameMessageHandler; @@ -97,6 +98,7 @@ public class OngekiController { this.getUserRegionHandler = getUserRegionHandler; this.getUserRivalHandler = getUserRivalHandler; this.getUserRivalMusicHandler = getUserRivalMusicHandler; + this.getUserRivalDataHandler = getUserRivalDataHandler; this.getUserScenarioHandler = getUserScenarioHandler; this.getUserStoryHandler = getUserStoryHandler; this.getUserTechCountHandler = getUserTechCountHandler; @@ -308,13 +310,11 @@ public class OngekiController { return getUserRivalHandler.handle(request); } - // seems same as GetUserRivalApi @PostMapping("GetUserRivalDataApi") public String getUserRivalData(@ModelAttribute Map request) throws JsonProcessingException { - return getUserRivalHandler.handle(request); + return getUserRivalDataHandler.handle(request); } - // dummy for now @PostMapping("GetUserRivalMusicApi") public String getUserRivalMusic(@ModelAttribute Map request) throws JsonProcessingException { return getUserRivalMusicHandler.handle(request); diff --git a/src/main/java/icu/samnyan/aqua/sega/ongeki/dao/userdata/UserDataRepository.java b/src/main/java/icu/samnyan/aqua/sega/ongeki/dao/userdata/UserDataRepository.java index a0dadf0d..a348c206 100644 --- a/src/main/java/icu/samnyan/aqua/sega/ongeki/dao/userdata/UserDataRepository.java +++ b/src/main/java/icu/samnyan/aqua/sega/ongeki/dao/userdata/UserDataRepository.java @@ -6,6 +6,8 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; +import java.util.Collection; +import java.util.List; import java.util.Optional; /** @@ -13,6 +15,7 @@ import java.util.Optional; */ @Repository("OngekiUserDataRepository") public interface UserDataRepository extends JpaRepository { + List findByCard_ExtIdIn(Collection userIds); Optional findByCard(Card card); diff --git a/src/main/java/icu/samnyan/aqua/sega/ongeki/dao/userdata/UserRivalDataRepository.java b/src/main/java/icu/samnyan/aqua/sega/ongeki/dao/userdata/UserRivalDataRepository.java new file mode 100644 index 00000000..72b91e10 --- /dev/null +++ b/src/main/java/icu/samnyan/aqua/sega/ongeki/dao/userdata/UserRivalDataRepository.java @@ -0,0 +1,24 @@ +package icu.samnyan.aqua.sega.ongeki.dao.userdata; + +import icu.samnyan.aqua.sega.ongeki.model.userdata.UserData; +import icu.samnyan.aqua.sega.ongeki.model.userdata.UserRival; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * @author dp (privateamusement@protonmail.com) + */ +@Repository("OngekiUserRivalDataRepository") +public interface UserRivalDataRepository extends JpaRepository { + List findByUser_Card_ExtId(long userId); + + @Transactional + void removeByUser_Card_ExtIdAndRivalUserId(long userId,long rivalUserId); + + @Transactional + void deleteByUser(UserData user); +} diff --git a/src/main/java/icu/samnyan/aqua/sega/ongeki/handler/impl/GetUserRivalDataHandler.java b/src/main/java/icu/samnyan/aqua/sega/ongeki/handler/impl/GetUserRivalDataHandler.java new file mode 100644 index 00000000..eaa8b875 --- /dev/null +++ b/src/main/java/icu/samnyan/aqua/sega/ongeki/handler/impl/GetUserRivalDataHandler.java @@ -0,0 +1,63 @@ +package icu.samnyan.aqua.sega.ongeki.handler.impl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import icu.samnyan.aqua.sega.ongeki.dao.userdata.UserDataRepository; +import icu.samnyan.aqua.sega.ongeki.dao.userdata.UserRivalDataRepository; +import icu.samnyan.aqua.sega.ongeki.handler.BaseHandler; +import icu.samnyan.aqua.sega.ongeki.model.response.data.UserRivalData; +import icu.samnyan.aqua.sega.util.jackson.BasicMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author GEEKiDoS (geek_ds@foxmail.com) + */ +@Component("OngekiGetUserRivalDataHandler") +public class GetUserRivalDataHandler implements BaseHandler { + private static final Logger logger = LoggerFactory.getLogger(GetUserRivalDataHandler.class); + + private final BasicMapper mapper; + private final UserRivalDataRepository userRivalDataRepository; + private final UserDataRepository userDataRepository; + + @Autowired + public GetUserRivalDataHandler(BasicMapper mapper, UserRivalDataRepository userRivalDataRepository, UserDataRepository userDataRepository) { + this.mapper = mapper; + this.userRivalDataRepository = userRivalDataRepository; + this.userDataRepository = userDataRepository; + } + + @Override + public String handle(Map request) throws JsonProcessingException { + var userRivalId = ((Number) request.get("userId")).longValue(); + var userRivalList = ((Collection>)request.get("userRivalList")) + .stream() + .map(x->((Number)x.get("rivalUserId")).longValue()) + .collect(Collectors.toList()); + + Map resultMap = new LinkedHashMap<>(); + resultMap.put("userId", userRivalId); + + var userInfos = userDataRepository + .findByCard_ExtIdIn(userRivalList) + .stream() + .map(x -> new UserRivalData(x.getCard().getExtId().longValue(), x.getUserName())) + .toArray(); + + resultMap.put("length", userInfos.length); + resultMap.put("userRivalDataList", userInfos); + + String json = mapper.write(resultMap); + + logger.info("Response: " + json); + return json; + } +} diff --git a/src/main/java/icu/samnyan/aqua/sega/ongeki/handler/impl/GetUserRivalHandler.java b/src/main/java/icu/samnyan/aqua/sega/ongeki/handler/impl/GetUserRivalHandler.java index 47de4f80..805abddb 100644 --- a/src/main/java/icu/samnyan/aqua/sega/ongeki/handler/impl/GetUserRivalHandler.java +++ b/src/main/java/icu/samnyan/aqua/sega/ongeki/handler/impl/GetUserRivalHandler.java @@ -1,6 +1,7 @@ package icu.samnyan.aqua.sega.ongeki.handler.impl; import com.fasterxml.jackson.core.JsonProcessingException; +import icu.samnyan.aqua.sega.ongeki.dao.userdata.UserRivalDataRepository; import icu.samnyan.aqua.sega.ongeki.handler.BaseHandler; import icu.samnyan.aqua.sega.util.jackson.BasicMapper; import org.slf4j.Logger; @@ -20,10 +21,12 @@ public class GetUserRivalHandler implements BaseHandler { private static final Logger logger = LoggerFactory.getLogger(GetUserRivalHandler.class); private final BasicMapper mapper; + private final UserRivalDataRepository userRivalDataRepository; @Autowired - public GetUserRivalHandler(BasicMapper mapper) { + public GetUserRivalHandler(BasicMapper mapper, UserRivalDataRepository userRivalDataRepository) { this.mapper = mapper; + this.userRivalDataRepository = userRivalDataRepository; } @Override @@ -32,8 +35,10 @@ public class GetUserRivalHandler implements BaseHandler { Map resultMap = new LinkedHashMap<>(); resultMap.put("userId", userRivalId); - resultMap.put("length", 0); - resultMap.put("userRivalList", new List[]{}); + + var result = userRivalDataRepository.findByUser_Card_ExtId(userRivalId);//.stream().map(x->x.getUser().getCard().getExtId()).toArray(); + resultMap.put("length", result.size()); + resultMap.put("userRivalList", result); String json = mapper.write(resultMap); diff --git a/src/main/java/icu/samnyan/aqua/sega/ongeki/handler/impl/GetUserRivalMusicHandler.java b/src/main/java/icu/samnyan/aqua/sega/ongeki/handler/impl/GetUserRivalMusicHandler.java index 8d02e450..fe3688b3 100644 --- a/src/main/java/icu/samnyan/aqua/sega/ongeki/handler/impl/GetUserRivalMusicHandler.java +++ b/src/main/java/icu/samnyan/aqua/sega/ongeki/handler/impl/GetUserRivalMusicHandler.java @@ -1,16 +1,19 @@ package icu.samnyan.aqua.sega.ongeki.handler.impl; import com.fasterxml.jackson.core.JsonProcessingException; +import icu.samnyan.aqua.sega.ongeki.dao.userdata.UserMusicDetailRepository; import icu.samnyan.aqua.sega.ongeki.handler.BaseHandler; +import icu.samnyan.aqua.sega.ongeki.model.response.data.UserRivalMusic; import icu.samnyan.aqua.sega.util.jackson.BasicMapper; +import icu.samnyan.aqua.spring.data.OffsetPageRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Component; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; /** * @author GEEKiDoS (geek_ds@foxmail.com) @@ -20,26 +23,57 @@ public class GetUserRivalMusicHandler implements BaseHandler { private static final Logger logger = LoggerFactory.getLogger(GetUserRivalMusicHandler.class); private final BasicMapper mapper; + private final UserMusicDetailRepository userMusicDetailRepository; @Autowired - public GetUserRivalMusicHandler(BasicMapper mapper) { + public GetUserRivalMusicHandler(BasicMapper mapper, UserMusicDetailRepository userMusicDetailRepository) { this.mapper = mapper; + this.userMusicDetailRepository = userMusicDetailRepository; } - @Override public String handle(Map request) throws JsonProcessingException { - var userId = (long)request.get("userId"); - var rivalUserId = (int)request.get("rivalUserId"); -/* var nextIndex = (int)request.get("nextIndex"); - var maxCount = (int)request.get("maxCount"); */ + var userId = ((Number) request.get("userId")).longValue(); + var rivalUserId = ((Number) request.get("rivalUserId")).longValue(); + var nextIndex = ((Number) request.get("nextIndex")).intValue(); + var maxCount = ((Number) request.get("maxCount")).intValue(); + + //fix maxCount and sort by musicId so that we could fetch entity music with full fumen diffs. + var fixedMaxCount = maxCount + (maxCount % 5 == 0 ? 0 : 5); + var sorter = Sort.by(Sort.Direction.ASC, "musicId"); + + var groupedMusicDetailsItor = userMusicDetailRepository + .findByUser_Card_ExtId(rivalUserId, OffsetPageRequest.of(nextIndex, fixedMaxCount, sorter)) + .stream() + .collect(Collectors.groupingBy(x -> x.getMusicId())) + .values() + .stream() + .map(x -> new UserRivalMusic(x,x.size())) + .iterator(); + + var filterMusicDetails = new ArrayList(); + + var actualReadCount = 0; + while(groupedMusicDetailsItor.hasNext()){ + var musicDetail = groupedMusicDetailsItor.next(); + if (musicDetail.getLength() + actualReadCount > maxCount) + break; + filterMusicDetails.add(musicDetail); + actualReadCount += musicDetail.getLength(); + } + + var respNextIndex = nextIndex + actualReadCount; + if (filterMusicDetails.size() <= 0) + respNextIndex = 0; //nofity client that All music details had been sent (for this rival). + else + filterMusicDetails.sort(Comparator.comparingInt(a -> a.getUserRivalMusicDetailList().get(0).getMusicId())); Map resultMap = new LinkedHashMap<>(); resultMap.put("userId", userId); resultMap.put("rivalUserId", rivalUserId); - resultMap.put("length", 0); - resultMap.put("nextIndex", 0); - resultMap.put("userRivalMusicList", new List[]{}); + resultMap.put("length", filterMusicDetails.size()); + resultMap.put("nextIndex", respNextIndex); + resultMap.put("userRivalMusicList", filterMusicDetails); String json = mapper.write(resultMap); diff --git a/src/main/java/icu/samnyan/aqua/sega/ongeki/model/response/data/GameSetting.java b/src/main/java/icu/samnyan/aqua/sega/ongeki/model/response/data/GameSetting.java index 0c7a5068..d6ef6d52 100644 --- a/src/main/java/icu/samnyan/aqua/sega/ongeki/model/response/data/GameSetting.java +++ b/src/main/java/icu/samnyan/aqua/sega/ongeki/model/response/data/GameSetting.java @@ -27,5 +27,5 @@ public class GameSetting { private int maxCountItem; private int maxCountMusic; private int maxCountMusicItem; - private int macCountRivalMusic; + private int maxCountRivalMusic; } diff --git a/src/main/java/icu/samnyan/aqua/sega/ongeki/model/response/data/UserRivalData.java b/src/main/java/icu/samnyan/aqua/sega/ongeki/model/response/data/UserRivalData.java new file mode 100644 index 00000000..6e1ae559 --- /dev/null +++ b/src/main/java/icu/samnyan/aqua/sega/ongeki/model/response/data/UserRivalData.java @@ -0,0 +1,16 @@ +package icu.samnyan.aqua.sega.ongeki.model.response.data; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author samnyan (privateamusement@protonmail.com) + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UserRivalData { + private long rivalUserId; + private String rivalUserName; +} diff --git a/src/main/java/icu/samnyan/aqua/sega/ongeki/model/response/data/UserRivalMusic.java b/src/main/java/icu/samnyan/aqua/sega/ongeki/model/response/data/UserRivalMusic.java new file mode 100644 index 00000000..3dd0e4ca --- /dev/null +++ b/src/main/java/icu/samnyan/aqua/sega/ongeki/model/response/data/UserRivalMusic.java @@ -0,0 +1,19 @@ +package icu.samnyan.aqua.sega.ongeki.model.response.data; + +import icu.samnyan.aqua.sega.ongeki.model.userdata.UserMusicDetail; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author samnyan (privateamusement@protonmail.com) + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UserRivalMusic { + private List userRivalMusicDetailList; + private int length; +} diff --git a/src/main/java/icu/samnyan/aqua/sega/ongeki/model/userdata/UserRival.java b/src/main/java/icu/samnyan/aqua/sega/ongeki/model/userdata/UserRival.java new file mode 100644 index 00000000..b025167a --- /dev/null +++ b/src/main/java/icu/samnyan/aqua/sega/ongeki/model/userdata/UserRival.java @@ -0,0 +1,38 @@ +package icu.samnyan.aqua.sega.ongeki.model.userdata; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import icu.samnyan.aqua.sega.util.jackson.UserIdSerializer; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.*; +import java.io.Serializable; + +/** + * @author samnyan (privateamusement@protonmail.com) + */ +@Entity(name = "OngekiUserRival") +@Table(name = "ongeki_user_rival") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class UserRival implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @JsonIgnore + private long id; + + @ManyToOne + @JsonIgnore + @JoinColumn(name = "user_id") + private UserData user; + + @JoinColumn(name = "rival_user_id") + @JsonProperty("rivalUserId") + private long rivalUserId; +} diff --git a/src/main/java/icu/samnyan/aqua/sega/util/jackson/UserIdSerializer.java b/src/main/java/icu/samnyan/aqua/sega/util/jackson/UserIdSerializer.java new file mode 100644 index 00000000..c17a1841 --- /dev/null +++ b/src/main/java/icu/samnyan/aqua/sega/util/jackson/UserIdSerializer.java @@ -0,0 +1,27 @@ +package icu.samnyan.aqua.sega.util.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import icu.samnyan.aqua.sega.ongeki.model.userdata.UserData; + +import java.io.IOException; + +/** + * @author samnyan (privateamusement@protonmail.com) + */ +public class UserIdSerializer extends StdSerializer { + + public UserIdSerializer() { + this(null); + } + + public UserIdSerializer(Class t) { + super(t); + } + + @Override + public void serialize(UserData value, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeNumber(value.getCard().getExtId()); + } +} diff --git a/src/main/resources/db/migration/mariadb/V222__add_ongeki_user_rival.sql b/src/main/resources/db/migration/mariadb/V222__add_ongeki_user_rival.sql new file mode 100644 index 00000000..3d117fea --- /dev/null +++ b/src/main/resources/db/migration/mariadb/V222__add_ongeki_user_rival.sql @@ -0,0 +1,32 @@ +-- -------------------------------------------------------- +-- 主机: 127.0.0.1 +-- 服务器版本: 10.10.2-MariaDB - mariadb.org binary distribution +-- 服务器操作系统: Win64 +-- HeidiSQL 版本: 11.3.0.6295 +-- -------------------------------------------------------- + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET NAMES utf8 */; +/*!50503 SET NAMES utf8mb4 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- 导出 表 aqua.ongeki_user_rival 结构 +CREATE TABLE IF NOT EXISTS `ongeki_user_rival` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` bigint(20) NOT NULL, + `rival_user_id` bigint(20) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `ongeki_user_rival_uq` (`user_id`,`rival_user_id`), + KEY `FK__ongeki_user_data_2` (`rival_user_id`), + CONSTRAINT `FK__ongeki_user_data` FOREIGN KEY (`user_id`) REFERENCES `ongeki_user_data` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT `FK__ongeki_user_data_2` FOREIGN KEY (`rival_user_id`) REFERENCES `ongeki_user_data` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- 数据导出被取消选择。 + +/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; +/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */; \ No newline at end of file diff --git a/src/main/resources/db/migration/mysql/V222__add_ongeki_user_rival.sql b/src/main/resources/db/migration/mysql/V222__add_ongeki_user_rival.sql new file mode 100644 index 00000000..3d117fea --- /dev/null +++ b/src/main/resources/db/migration/mysql/V222__add_ongeki_user_rival.sql @@ -0,0 +1,32 @@ +-- -------------------------------------------------------- +-- 主机: 127.0.0.1 +-- 服务器版本: 10.10.2-MariaDB - mariadb.org binary distribution +-- 服务器操作系统: Win64 +-- HeidiSQL 版本: 11.3.0.6295 +-- -------------------------------------------------------- + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET NAMES utf8 */; +/*!50503 SET NAMES utf8mb4 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- 导出 表 aqua.ongeki_user_rival 结构 +CREATE TABLE IF NOT EXISTS `ongeki_user_rival` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` bigint(20) NOT NULL, + `rival_user_id` bigint(20) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `ongeki_user_rival_uq` (`user_id`,`rival_user_id`), + KEY `FK__ongeki_user_data_2` (`rival_user_id`), + CONSTRAINT `FK__ongeki_user_data` FOREIGN KEY (`user_id`) REFERENCES `ongeki_user_data` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT `FK__ongeki_user_data_2` FOREIGN KEY (`rival_user_id`) REFERENCES `ongeki_user_data` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- 数据导出被取消选择。 + +/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; +/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */; \ No newline at end of file diff --git a/src/main/resources/db/migration/sqlite/V222__add_ongeki_user_rival.sql b/src/main/resources/db/migration/sqlite/V222__add_ongeki_user_rival.sql new file mode 100644 index 00000000..08e63a83 --- /dev/null +++ b/src/main/resources/db/migration/sqlite/V222__add_ongeki_user_rival.sql @@ -0,0 +1,9 @@ +CREATE TABLE "ongeki_user_rival" ( + "id" INTEGER NOT NULL, + "rival_user_id" BIGINT, + "user_id" BIGINT, + FOREIGN KEY("user_id") REFERENCES "ongeki_user_data"("id") ON DELETE CASCADE, + FOREIGN KEY("rival_user_id") REFERENCES "ongeki_user_data"("id") ON DELETE CASCADE, + PRIMARY KEY("id" AUTOINCREMENT), + CONSTRAINT "ongeki_user_rival_uq" UNIQUE("user_id","rival_user_id") ON CONFLICT REPLACE +); \ No newline at end of file diff --git a/src/test/java/icu/samnyan/aqua/sega/ongeki/dao/userdata/OngekiRepositoryTest.java b/src/test/java/icu/samnyan/aqua/sega/ongeki/dao/userdata/OngekiRepositoryTest.java index 21f28d33..f92b86ba 100644 --- a/src/test/java/icu/samnyan/aqua/sega/ongeki/dao/userdata/OngekiRepositoryTest.java +++ b/src/test/java/icu/samnyan/aqua/sega/ongeki/dao/userdata/OngekiRepositoryTest.java @@ -3,6 +3,7 @@ package icu.samnyan.aqua.sega.ongeki.dao.userdata; import icu.samnyan.aqua.sega.general.dao.CardRepository; import icu.samnyan.aqua.sega.general.model.Card; import icu.samnyan.aqua.sega.ongeki.model.userdata.*; +import icu.samnyan.aqua.util.CardHelper; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; @@ -63,6 +64,8 @@ class OngekiRepositoryTest { private UserTechCountRepository userTechCountRepository; @Autowired private UserTrainingRoomRepository userTrainingRoomRepository; + @Autowired + private UserRivalDataRepository userRivalDataRepository; @Test void userData_SaveLoad() { @@ -337,10 +340,35 @@ class OngekiRepositoryTest { assertThat(aL).hasSize(2); } + @Test + void userRivalData_SaveLoad() { + var u = getNewRandomValidUser(); + var r1 = getNewRandomValidUser(); + var r2 = getNewRandomValidUser(); + var r3 = getNewRandomValidUser(); + + userRivalDataRepository.saveAll(List.of( + getUserRival(u, r1), + getUserRival(u, r2), + getUserRival(r1, r2), + getUserRival(r2, u) + )); + + var all = userRivalDataRepository.findAll(); + assertThat(all).hasSize(4); + + var find = userRivalDataRepository.findByUser_Card_ExtId(u.getCard().getExtId()); + assertThat(find).hasSize(2); + } + private UserData getUser(Card c) { return new UserData(-1, c, "Hello", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "2020", "2020", "SDDT", "1.00.00", "1.00.00", "2020", "SDDT", "1.00.00", "1.00.00", "", "2020", 0, "0", 0, "123", 0, "A000000", 0, 0, 0); } + private UserData getNewRandomValidUser() { + return userDataRepository.save(getUser(cardRepository.save(CardHelper.getRandomCard()))); + } + private UserActivity getActivity(UserData u, Integer activityId) { return new UserActivity(-1, u, 1, activityId, 0, 0, 0, 0, 0); } @@ -416,4 +444,8 @@ class OngekiRepositoryTest { private UserTrainingRoom getTrainingRoom(UserData u, Integer roomId) { return new UserTrainingRoom(-1, u, "", roomId, 1, ""); } + + private UserRival getUserRival(UserData user, UserData rival) { + return new UserRival(0, user, rival.getCard().getExtId()); + } } \ No newline at end of file diff --git a/src/test/java/icu/samnyan/aqua/util/CardHelper.java b/src/test/java/icu/samnyan/aqua/util/CardHelper.java index d6e71882..bb227668 100644 --- a/src/test/java/icu/samnyan/aqua/util/CardHelper.java +++ b/src/test/java/icu/samnyan/aqua/util/CardHelper.java @@ -3,15 +3,30 @@ package icu.samnyan.aqua.util; import icu.samnyan.aqua.sega.general.model.Card; import java.time.LocalDateTime; +import java.util.Random; /** * @author sam_nya (privateamusement@protonmail.com) */ public class CardHelper { + private static Random rand = new Random(); public static Card getCard() { var now = LocalDateTime.now(); return new Card(1L, 114514L, "01145141919810000000", now, now); } + public static Card getRandomCard() { + var now = LocalDateTime.now(); + + var luid = ""; + for (int i = 0; i < "01145141919810028570".length(); i++) + luid += rand.nextInt(10); + + var extId = 0L; + for (int i = 0; i < "114514".length(); i++) + extId = extId * 10 + rand.nextInt(10); + + return new Card(0, extId, luid, now, now); + } }