diff --git a/.idea/.cache/.Apifox_Helper/.toolWindow.db b/.idea/.cache/.Apifox_Helper/.toolWindow.db index b2028a7..80e1644 100644 Binary files a/.idea/.cache/.Apifox_Helper/.toolWindow.db and b/.idea/.cache/.Apifox_Helper/.toolWindow.db differ diff --git a/.idea/ApifoxUploaderProjectSetting.xml b/.idea/ApifoxUploaderProjectSetting.xml index 40b8ae2..be81dad 100644 --- a/.idea/ApifoxUploaderProjectSetting.xml +++ b/.idea/ApifoxUploaderProjectSetting.xml @@ -1,12 +1,13 @@ - - \ No newline at end of file diff --git a/src/main/java/vvpkassistant/config/FunctionConfigHolder.java b/src/main/java/vvpkassistant/config/FunctionConfigHolder.java index a316318..d3f11e8 100644 --- a/src/main/java/vvpkassistant/config/FunctionConfigHolder.java +++ b/src/main/java/vvpkassistant/config/FunctionConfigHolder.java @@ -1,5 +1,6 @@ package vvpkassistant.config; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import vvpkassistant.FunctionConfig.mapper.FunctionConfigMapper; @@ -11,9 +12,10 @@ import java.util.concurrent.CopyOnWriteArrayList; @Component @RequiredArgsConstructor +@Slf4j public class FunctionConfigHolder { // 线程安全的全局配置容器 - public static final List CONFIGS = new CopyOnWriteArrayList<>(); + public static List CONFIGS = new CopyOnWriteArrayList<>(); @Autowired private FunctionConfigMapper configMapper; @@ -26,7 +28,7 @@ public class FunctionConfigHolder { List dbConfigs = configMapper.selectList(null); CONFIGS.clear(); CONFIGS.addAll(dbConfigs); - System.out.println("已加载 "+CONFIGS.size()+" 条功能配置"); + log.info("已加载 {} 条功能配置", CONFIGS.size()); } /** diff --git a/src/main/java/vvpkassistant/config/SaTokenConfigure.java b/src/main/java/vvpkassistant/config/SaTokenConfigure.java index a60a36d..f862d64 100644 --- a/src/main/java/vvpkassistant/config/SaTokenConfigure.java +++ b/src/main/java/vvpkassistant/config/SaTokenConfigure.java @@ -62,7 +62,8 @@ public class SaTokenConfigure implements WebMvcConfigurer { "/systemMessage/list", "/pk/pkListForPython", "/pk/insertPkDetail", - "/pk/updatePkRecordInfo" + "/pk/updatePkRecordInfo", + "/pk/grantPkResultPoints" }; } diff --git a/src/main/java/vvpkassistant/controller/PkController.java b/src/main/java/vvpkassistant/controller/PkController.java index bc9fd69..a6d61c4 100644 --- a/src/main/java/vvpkassistant/controller/PkController.java +++ b/src/main/java/vvpkassistant/controller/PkController.java @@ -1,11 +1,9 @@ package vvpkassistant.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; -import vvpkassistant.CoinRecords.CoinRecordsDao; import vvpkassistant.Data.ResponseData; import vvpkassistant.Data.ResponseInfo; import vvpkassistant.Tools.VVTools; -import vvpkassistant.User.mapper.UserDao; import vvpkassistant.pk.mapper.PkInfoDao; import vvpkassistant.pk.mapper.PkRecordDao; import vvpkassistant.pk.mapper.PkRecordDetailDao; @@ -15,10 +13,12 @@ import vvpkassistant.pk.model.DTO.PkInfoDetailDTO; import vvpkassistant.pk.model.DTO.PkListRequestDTO; import vvpkassistant.pk.model.DTO.PkListUninvitedDTO; import vvpkassistant.pk.model.DTO.PkQueryMyCanUseDTO; +import vvpkassistant.pk.model.DTO.PkResultPointsDTO; import vvpkassistant.pk.model.PkInfoModel; import vvpkassistant.pk.model.PkRecord; import vvpkassistant.pk.model.PkRecordDetail; import vvpkassistant.pk.service.PKService; +import vvpkassistant.pk.service.PkResultPointService; import javax.annotation.Resource; import java.util.List; @@ -32,6 +32,9 @@ public class PkController { @Resource private PKService pkService; + @Resource + private PkResultPointService pkResultPointService; + @Autowired private PkInfoDao pkDao; @@ -41,12 +44,6 @@ public class PkController { @Autowired private PkRecordDetailDao detailDao; - @Autowired - private UserDao userDao; - - @Autowired - private CoinRecordsDao coinRecordsDao; - // 创建pk数据 @PostMapping("addPkData") public ResponseData addPkData(@RequestBody PkInfoModel pkModel) { @@ -155,6 +152,12 @@ public class PkController { return i == 1 ? ResponseData.success("") : ResponseData.error(ResponseInfo.ERROR.getCode(),null); } + // 根据PK结果为用户增加积分 + @PostMapping("grantPkResultPoints") + public ResponseData grantPkResultPoints(@RequestBody PkResultPointsDTO request) { + return ResponseData.success(pkResultPointService.grantPkResultPoints(request)); + } + // 插入pk明细表数据 @PostMapping("insertPkDetail") public ResponseData insert(@RequestBody PkRecordDetail detail) { diff --git a/src/main/java/vvpkassistant/pk/model/DTO/PkResultPointsDTO.java b/src/main/java/vvpkassistant/pk/model/DTO/PkResultPointsDTO.java new file mode 100644 index 0000000..ed061c7 --- /dev/null +++ b/src/main/java/vvpkassistant/pk/model/DTO/PkResultPointsDTO.java @@ -0,0 +1,9 @@ +package vvpkassistant.pk.model.DTO; + +import lombok.Data; + +@Data +public class PkResultPointsDTO { + private Integer winnerUserId; + private Integer loserUserId; +} diff --git a/src/main/java/vvpkassistant/pk/service/PKServiceImpl.java b/src/main/java/vvpkassistant/pk/service/PKServiceImpl.java index 70529ea..3420aa8 100644 --- a/src/main/java/vvpkassistant/pk/service/PKServiceImpl.java +++ b/src/main/java/vvpkassistant/pk/service/PKServiceImpl.java @@ -4,8 +4,6 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; import vvpkassistant.CoinRecords.CoinRecords; import vvpkassistant.CoinRecords.CoinRecordsDao; -import vvpkassistant.Data.ResponseData; -import vvpkassistant.Data.ResponseInfo; import vvpkassistant.Tools.VVTools; import vvpkassistant.User.mapper.UserDao; import vvpkassistant.User.model.UserModel; @@ -242,4 +240,5 @@ public class PKServiceImpl extends ServiceImpl implement return pkInfoModel; } } + } diff --git a/src/main/java/vvpkassistant/pk/service/PkResultPointService.java b/src/main/java/vvpkassistant/pk/service/PkResultPointService.java new file mode 100644 index 0000000..8cc6174 --- /dev/null +++ b/src/main/java/vvpkassistant/pk/service/PkResultPointService.java @@ -0,0 +1,7 @@ +package vvpkassistant.pk.service; + +import vvpkassistant.pk.model.DTO.PkResultPointsDTO; + +public interface PkResultPointService { + String grantPkResultPoints(PkResultPointsDTO request); +} diff --git a/src/main/java/vvpkassistant/pk/service/PkResultPointServiceImpl.java b/src/main/java/vvpkassistant/pk/service/PkResultPointServiceImpl.java new file mode 100644 index 0000000..12528d3 --- /dev/null +++ b/src/main/java/vvpkassistant/pk/service/PkResultPointServiceImpl.java @@ -0,0 +1,85 @@ +package vvpkassistant.pk.service; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import vvpkassistant.CoinRecords.CoinRecords; +import vvpkassistant.CoinRecords.CoinRecordsDao; +import vvpkassistant.Tools.EpochSecondProvider; +import vvpkassistant.User.mapper.UserDao; +import vvpkassistant.common.ErrorCode; +import vvpkassistant.config.FunctionConfigProvider; +import vvpkassistant.exception.BusinessException; +import vvpkassistant.pk.model.DTO.PkResultPointsDTO; + +@Service +public class PkResultPointServiceImpl implements PkResultPointService { + + private static final String PK_WIN_COIN_CONFIG_NAME = "PK胜利增加积分"; + private static final String PK_LOSE_COIN_CONFIG_NAME = "PK失败增加积分"; + private static final int COIN_RECORD_ADD = 1; + + private final UserDao userDao; + private final CoinRecordsDao coinRecordsDao; + private final FunctionConfigProvider functionConfigProvider; + private final EpochSecondProvider epochSecondProvider; + + public PkResultPointServiceImpl( + UserDao userDao, + CoinRecordsDao coinRecordsDao, + FunctionConfigProvider functionConfigProvider, + EpochSecondProvider epochSecondProvider + ) { + this.userDao = userDao; + this.coinRecordsDao = coinRecordsDao; + this.functionConfigProvider = functionConfigProvider; + this.epochSecondProvider = epochSecondProvider; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String grantPkResultPoints(PkResultPointsDTO request) { + validateRequest(request); + int now = (int) epochSecondProvider.nowEpochSecond(); + int winPoints = parsePositivePoints(PK_WIN_COIN_CONFIG_NAME); + int losePoints = parsePositivePoints(PK_LOSE_COIN_CONFIG_NAME); + + grantPoints(request.getWinnerUserId(), winPoints, PK_WIN_COIN_CONFIG_NAME, now); + grantPoints(request.getLoserUserId(), losePoints, PK_LOSE_COIN_CONFIG_NAME, now); + return String.format("操作成功,胜利方增加%d积分,失败方增加%d积分", winPoints, losePoints); + } + + private static void validateRequest(PkResultPointsDTO request) { + if (request == null || request.getWinnerUserId() == null || request.getLoserUserId() == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数不能为空"); + } + if (request.getWinnerUserId().equals(request.getLoserUserId())) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "胜利者和失败者不能是同一用户"); + } + } + + private int parsePositivePoints(String configName) { + String value = functionConfigProvider.getValue(configName); + if (value == null) { + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "未配置" + configName); + } + try { + int points = Integer.parseInt(value); + if (points <= 0) { + throw new BusinessException(ErrorCode.SYSTEM_ERROR, configName + "配置错误"); + } + return points; + } catch (NumberFormatException e) { + throw new BusinessException(ErrorCode.SYSTEM_ERROR, configName + "配置错误"); + } + } + + private void grantPoints(int userId, int points, String configName, int now) { + if (userDao.increasePoints(userId, points) != 1) { + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "用户不存在"); + } + CoinRecords coinRecords = new CoinRecords(configName, userId, points, now, COIN_RECORD_ADD); + if (coinRecordsDao.insert(coinRecords) != 1) { + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "积分记录写入失败"); + } + } +} diff --git a/src/test/java/vvpkassistant/pk/service/PkResultPointServiceImplTests.java b/src/test/java/vvpkassistant/pk/service/PkResultPointServiceImplTests.java new file mode 100644 index 0000000..b81fc3a --- /dev/null +++ b/src/test/java/vvpkassistant/pk/service/PkResultPointServiceImplTests.java @@ -0,0 +1,75 @@ +package vvpkassistant.pk.service; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import vvpkassistant.CoinRecords.CoinRecords; +import vvpkassistant.CoinRecords.CoinRecordsDao; +import vvpkassistant.Tools.EpochSecondProvider; +import vvpkassistant.User.mapper.UserDao; +import vvpkassistant.common.ErrorCode; +import vvpkassistant.config.FunctionConfigProvider; +import vvpkassistant.exception.BusinessException; +import vvpkassistant.pk.model.DTO.PkResultPointsDTO; + +import static org.mockito.Mockito.*; + +class PkResultPointServiceImplTests { + + @Test + void shouldGrantWinAndLosePointsTogether() { + UserDao userDao = mock(UserDao.class); + CoinRecordsDao coinRecordsDao = mock(CoinRecordsDao.class); + FunctionConfigProvider functionConfigProvider = mock(FunctionConfigProvider.class); + EpochSecondProvider epochSecondProvider = () -> 1_234L; + + when(functionConfigProvider.getValue("PK胜利增加积分")).thenReturn("8"); + when(functionConfigProvider.getValue("PK失败增加积分")).thenReturn("3"); + when(userDao.increasePoints(1001, 8)).thenReturn(1); + when(userDao.increasePoints(1002, 3)).thenReturn(1); + when(coinRecordsDao.insert(any())).thenReturn(1); + + PkResultPointServiceImpl service = new PkResultPointServiceImpl( + userDao, coinRecordsDao, functionConfigProvider, epochSecondProvider + ); + PkResultPointsDTO request = new PkResultPointsDTO(); + request.setWinnerUserId(1001); + request.setLoserUserId(1002); + + String result = service.grantPkResultPoints(request); + + Assertions.assertEquals("操作成功,胜利方增加8积分,失败方增加3积分", result); + ArgumentCaptor captor = ArgumentCaptor.forClass(CoinRecords.class); + verify(coinRecordsDao, times(2)).insert(captor.capture()); + Assertions.assertEquals("PK胜利增加积分", captor.getAllValues().get(0).getInfo()); + Assertions.assertEquals(Integer.valueOf(1001), captor.getAllValues().get(0).getUserId()); + Assertions.assertEquals(Integer.valueOf(8), captor.getAllValues().get(0).getNumber()); + Assertions.assertEquals("PK失败增加积分", captor.getAllValues().get(1).getInfo()); + Assertions.assertEquals(Integer.valueOf(1002), captor.getAllValues().get(1).getUserId()); + Assertions.assertEquals(Integer.valueOf(3), captor.getAllValues().get(1).getNumber()); + Assertions.assertEquals(Integer.valueOf(1234), captor.getAllValues().get(0).getTime()); + Assertions.assertEquals(Integer.valueOf(1234), captor.getAllValues().get(1).getTime()); + } + + @Test + void shouldFailWhenWinnerAndLoserAreSameUser() { + UserDao userDao = mock(UserDao.class); + CoinRecordsDao coinRecordsDao = mock(CoinRecordsDao.class); + FunctionConfigProvider functionConfigProvider = mock(FunctionConfigProvider.class); + EpochSecondProvider epochSecondProvider = () -> 1_234L; + + PkResultPointServiceImpl service = new PkResultPointServiceImpl( + userDao, coinRecordsDao, functionConfigProvider, epochSecondProvider + ); + PkResultPointsDTO request = new PkResultPointsDTO(); + request.setWinnerUserId(1001); + request.setLoserUserId(1001); + + BusinessException ex = Assertions.assertThrows(BusinessException.class, + () -> service.grantPkResultPoints(request)); + + Assertions.assertEquals(ErrorCode.PARAMS_ERROR.getCode(), ex.getCode()); + verifyNoInteractions(functionConfigProvider, coinRecordsDao); + verify(userDao, never()).increasePoints(anyInt(), anyInt()); + } +}