feat(pin): 重构置顶逻辑并抽离PkPinService
- 将UserController中置顶/取消置顶逻辑下沉到PkPinService,统一事务与异常处理 - UserDao新增原子增减积分方法,避免并发扣减问题 - VVTools抽取SECONDS_PER_HOUR常量并修复向上取整计算 - 新增EpochSecondProvider等接口与实现,为后续测试提供时钟桩 - 补充PkPinServiceImplTests单元测试,覆盖置顶成功、积分不足、重复取消等场景
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
package vvpkassistant.Tools;
|
||||||
|
|
||||||
|
public interface EpochSecondProvider {
|
||||||
|
long nowEpochSecond();
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package vvpkassistant.Tools;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class SystemEpochSecondProvider implements EpochSecondProvider {
|
||||||
|
@Override
|
||||||
|
public long nowEpochSecond() {
|
||||||
|
return VVTools.currentTimeStamp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -17,6 +17,8 @@ import java.util.Map;
|
|||||||
************************/
|
************************/
|
||||||
public class VVTools {
|
public class VVTools {
|
||||||
|
|
||||||
|
private static final long SECONDS_PER_HOUR = 3600L;
|
||||||
|
|
||||||
// 获取当前时间戳
|
// 获取当前时间戳
|
||||||
public static long currentTimeStamp() {
|
public static long currentTimeStamp() {
|
||||||
long timeStamp = Calendar.getInstance().getTimeInMillis() / 1000;
|
long timeStamp = Calendar.getInstance().getTimeInMillis() / 1000;
|
||||||
@@ -117,12 +119,12 @@ public class VVTools {
|
|||||||
public static long calculateHoursRound(long expireTime, long currentTime) {
|
public static long calculateHoursRound(long expireTime, long currentTime) {
|
||||||
if (expireTime <= currentTime) return 0;
|
if (expireTime <= currentTime) return 0;
|
||||||
long diffSeconds = expireTime - currentTime;
|
long diffSeconds = expireTime - currentTime;
|
||||||
return diffSeconds / 3600;
|
return (diffSeconds + SECONDS_PER_HOUR - 1) / SECONDS_PER_HOUR;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返还积分用(不足1小时忽略)
|
// 返还积分用(不足1小时忽略)
|
||||||
public static long calculateHoursFloor(long expireTime, long currentTime) {
|
public static long calculateHoursFloor(long expireTime, long currentTime) {
|
||||||
if (expireTime <= currentTime) return 0;
|
if (expireTime <= currentTime) return 0;
|
||||||
return (expireTime - currentTime) / 3600;
|
return (expireTime - currentTime) / SECONDS_PER_HOUR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,21 @@ import vvpkassistant.User.model.UserModel;
|
|||||||
@Mapper
|
@Mapper
|
||||||
public interface UserDao extends BaseMapper<UserModel> {
|
public interface UserDao extends BaseMapper<UserModel> {
|
||||||
|
|
||||||
|
// 原子扣减积分:当 points >= cost 时扣减,返回受影响行数(1=成功,0=积分不足/用户不存在)
|
||||||
|
default int decreasePointsIfEnough(Integer userId, int cost) {
|
||||||
|
return update(null, Wrappers.<UserModel>lambdaUpdate()
|
||||||
|
.eq(UserModel::getId, userId)
|
||||||
|
.ge(UserModel::getPoints, cost)
|
||||||
|
.setSql("points = points - " + cost));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原子增加积分:返回受影响行数(1=成功,0=用户不存在)
|
||||||
|
default int increasePoints(Integer userId, int amount) {
|
||||||
|
return update(null, Wrappers.<UserModel>lambdaUpdate()
|
||||||
|
.eq(UserModel::getId, userId)
|
||||||
|
.setSql("points = points + " + amount));
|
||||||
|
}
|
||||||
|
|
||||||
// 根据用户的手机号查询用户
|
// 根据用户的手机号查询用户
|
||||||
default UserModel queryWithPhoneNumber(String phoneNumber) {
|
default UserModel queryWithPhoneNumber(String phoneNumber) {
|
||||||
return selectOne(Wrappers.<UserModel>lambdaQuery()
|
return selectOne(Wrappers.<UserModel>lambdaQuery()
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package vvpkassistant.config;
|
||||||
|
|
||||||
|
public interface FunctionConfigProvider {
|
||||||
|
String getValue(String functionName);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package vvpkassistant.config;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class HolderBackedFunctionConfigProvider implements FunctionConfigProvider {
|
||||||
|
@Override
|
||||||
|
public String getValue(String functionName) {
|
||||||
|
return FunctionConfigHolder.getValue(functionName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -6,7 +6,6 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
import vvpkassistant.CoinRecords.CoinRecords;
|
import vvpkassistant.CoinRecords.CoinRecords;
|
||||||
import vvpkassistant.CoinRecords.CoinRecordsDao;
|
import vvpkassistant.CoinRecords.CoinRecordsDao;
|
||||||
import vvpkassistant.Data.ResponseData;
|
import vvpkassistant.Data.ResponseData;
|
||||||
import vvpkassistant.Data.ResponseInfo;
|
|
||||||
import vvpkassistant.Data.WxChatParam;
|
import vvpkassistant.Data.WxChatParam;
|
||||||
import vvpkassistant.User.mapper.UserDao;
|
import vvpkassistant.User.mapper.UserDao;
|
||||||
import vvpkassistant.User.mapper.SignInRecordDao;
|
import vvpkassistant.User.mapper.SignInRecordDao;
|
||||||
@@ -35,6 +34,7 @@ import vvpkassistant.pk.mapper.PkRecordDao;
|
|||||||
import vvpkassistant.pk.model.PkInfoModel;
|
import vvpkassistant.pk.model.PkInfoModel;
|
||||||
import vvpkassistant.pk.model.PkRecordDetail;
|
import vvpkassistant.pk.model.PkRecordDetail;
|
||||||
import vvpkassistant.pk.mapper.PkRecordDetailDao;
|
import vvpkassistant.pk.mapper.PkRecordDetailDao;
|
||||||
|
import vvpkassistant.pk.service.PkPinService;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -76,6 +76,9 @@ public class UserController {
|
|||||||
@Resource
|
@Resource
|
||||||
private MailService mailService;
|
private MailService mailService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private PkPinService pkPinService;
|
||||||
|
|
||||||
// 配置用户信息
|
// 配置用户信息
|
||||||
@PostMapping("inputUserInfo")
|
@PostMapping("inputUserInfo")
|
||||||
public ResponseData<Object> inputUserInfo(@RequestBody UserInputUserInfoDTO param) {
|
public ResponseData<Object> inputUserInfo(@RequestBody UserInputUserInfoDTO param) {
|
||||||
@@ -305,81 +308,23 @@ public class UserController {
|
|||||||
// 置顶文章
|
// 置顶文章
|
||||||
@PostMapping("pinToTop")
|
@PostMapping("pinToTop")
|
||||||
public ResponseData<Object> pinToTop(@RequestBody UserPinToTopDTO request) {
|
public ResponseData<Object> pinToTop(@RequestBody UserPinToTopDTO request) {
|
||||||
// 文章id
|
if (request == null || request.getArticleId() == null || request.getPinExpireTime() == null) {
|
||||||
Integer articleId = request.getArticleId();
|
throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数不能为空");
|
||||||
PkInfoModel pkInfoModel = pkInfoDao.selectById(articleId);
|
|
||||||
Integer userId = pkInfoModel.getSenderId();
|
|
||||||
// 到期时间戳
|
|
||||||
Integer pinExpireTime = request.getPinExpireTime();
|
|
||||||
|
|
||||||
long currentTimeStamp = VVTools.currentTimeStamp();
|
|
||||||
long hour = VVTools.calculateHoursRound(pinExpireTime, currentTimeStamp);
|
|
||||||
String coin = FunctionConfigHolder.getValue("置顶扣除积分");
|
|
||||||
int totalCoin = (int) (Integer.parseInt(coin) * hour);
|
|
||||||
|
|
||||||
UserModel userModel = userDao.selectById(userId);
|
|
||||||
if (userModel != null) {
|
|
||||||
// 扣除积分 更新数据
|
|
||||||
Integer points = userModel.getPoints();
|
|
||||||
if (points - totalCoin > 0) {
|
|
||||||
userModel.setPoints(userModel.getPoints() - totalCoin);
|
|
||||||
userDao.updateById(userModel);
|
|
||||||
// 设置置顶到期时间
|
|
||||||
pkInfoModel.setPinExpireTime(pinExpireTime);
|
|
||||||
// 设置创建置顶的时间
|
|
||||||
pkInfoModel.setPinCreateTime((int) VVTools.currentTimeStamp());
|
|
||||||
// 更新pk文章数据
|
|
||||||
int i = pkInfoDao.updateById(pkInfoModel);
|
|
||||||
if (i == 1) {
|
|
||||||
String info = String.format("置顶成功,扣除%d积分",totalCoin);
|
|
||||||
// 增加积分变动记录
|
|
||||||
CoinRecords coinRecords = new CoinRecords("置顶扣除积分",userId,totalCoin, (int) VVTools.currentTimeStamp(),0);
|
|
||||||
coinRecordsDao.insert(coinRecords);
|
|
||||||
// 返回给前端数据
|
|
||||||
return ResponseData.success(info);
|
|
||||||
}else {
|
|
||||||
throw new BusinessException(ErrorCode.SYSTEM_ERROR);
|
|
||||||
}
|
|
||||||
}else {
|
|
||||||
throw new BusinessException(ErrorCode.SYSTEM_ERROR,String.format("积分不足,需要%d积分",totalCoin));
|
|
||||||
}
|
|
||||||
}else {
|
|
||||||
throw new BusinessException(ErrorCode.SYSTEM_ERROR,"用户不存在");
|
|
||||||
}
|
}
|
||||||
|
int operatorUserId = Integer.parseInt(StpUtil.getLoginId().toString());
|
||||||
|
String info = pkPinService.pinToTop(operatorUserId, request.getArticleId(), request.getPinExpireTime());
|
||||||
|
return ResponseData.success(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 取消置顶
|
// 取消置顶
|
||||||
@PostMapping("cancelPin")
|
@PostMapping("cancelPin")
|
||||||
public ResponseData<Object> cancelPin(@RequestBody UserCancelPinDTO request) {
|
public ResponseData<Object> cancelPin(@RequestBody UserCancelPinDTO request) {
|
||||||
Integer articleId = request.getArticleId();
|
if (request == null || request.getArticleId() == null) {
|
||||||
PkInfoModel pkInfoModel = pkInfoDao.selectById(articleId);
|
throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数不能为空");
|
||||||
Integer pinExpireTime = pkInfoModel.getPinExpireTime();
|
|
||||||
long hour = VVTools.calculateHoursFloor(pinExpireTime, VVTools.currentTimeStamp());
|
|
||||||
|
|
||||||
String coin = FunctionConfigHolder.getValue("置顶扣除积分");
|
|
||||||
// 计算总积分。用于返还给用户
|
|
||||||
int totalCoin = (int) (Integer.parseInt(coin) * hour);
|
|
||||||
|
|
||||||
// 获取用户对象
|
|
||||||
UserModel userModel = userDao.selectById(pkInfoModel.getSenderId());
|
|
||||||
Integer points = userModel.getPoints();
|
|
||||||
// 返还用户积分
|
|
||||||
userModel.setPoints(points + totalCoin);
|
|
||||||
// 更新数据库
|
|
||||||
userDao.updateById(userModel);
|
|
||||||
|
|
||||||
// 重置置顶时间
|
|
||||||
pkInfoModel.setPinExpireTime(0);
|
|
||||||
pkInfoModel.setPinCreateTime(0);
|
|
||||||
int i = pkInfoDao.updateById(pkInfoModel);
|
|
||||||
if (i == 1) {
|
|
||||||
// 添加积分更变相关记录
|
|
||||||
CoinRecords coinRecords = new CoinRecords("取消置顶返还积分",pkInfoModel.getSenderId(),totalCoin, (int) VVTools.currentTimeStamp(),1);
|
|
||||||
coinRecordsDao.insert(coinRecords);
|
|
||||||
return ResponseData.success(String.format("操作成功,返还%d积分",totalCoin));
|
|
||||||
}else {
|
|
||||||
return ResponseData.error(ResponseInfo.ERROR.getCode(),null);
|
|
||||||
}
|
}
|
||||||
|
int operatorUserId = Integer.parseInt(StpUtil.getLoginId().toString());
|
||||||
|
String info = pkPinService.cancelPin(operatorUserId, request.getArticleId());
|
||||||
|
return ResponseData.success(info);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
8
src/main/java/vvpkassistant/pk/service/PkPinService.java
Normal file
8
src/main/java/vvpkassistant/pk/service/PkPinService.java
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package vvpkassistant.pk.service;
|
||||||
|
|
||||||
|
public interface PkPinService {
|
||||||
|
String pinToTop(int operatorUserId, int articleId, int pinExpireTime);
|
||||||
|
|
||||||
|
String cancelPin(int operatorUserId, int articleId);
|
||||||
|
}
|
||||||
|
|
||||||
167
src/main/java/vvpkassistant/pk/service/PkPinServiceImpl.java
Normal file
167
src/main/java/vvpkassistant/pk/service/PkPinServiceImpl.java
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
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.Tools.VVTools;
|
||||||
|
import vvpkassistant.User.mapper.UserDao;
|
||||||
|
import vvpkassistant.User.model.UserModel;
|
||||||
|
import vvpkassistant.common.ErrorCode;
|
||||||
|
import vvpkassistant.config.FunctionConfigProvider;
|
||||||
|
import vvpkassistant.exception.BusinessException;
|
||||||
|
import vvpkassistant.pk.mapper.PkInfoDao;
|
||||||
|
import vvpkassistant.pk.model.PkInfoModel;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PkPinServiceImpl implements PkPinService {
|
||||||
|
|
||||||
|
private static final String PIN_COIN_CONFIG_NAME = "置顶扣除积分";
|
||||||
|
private static final int COIN_RECORD_ADD = 1;
|
||||||
|
private static final int COIN_RECORD_DEDUCT = 0;
|
||||||
|
|
||||||
|
private final PkInfoDao pkInfoDao;
|
||||||
|
private final UserDao userDao;
|
||||||
|
private final CoinRecordsDao coinRecordsDao;
|
||||||
|
private final FunctionConfigProvider functionConfigProvider;
|
||||||
|
private final EpochSecondProvider epochSecondProvider;
|
||||||
|
|
||||||
|
public PkPinServiceImpl(
|
||||||
|
PkInfoDao pkInfoDao,
|
||||||
|
UserDao userDao,
|
||||||
|
CoinRecordsDao coinRecordsDao,
|
||||||
|
FunctionConfigProvider functionConfigProvider,
|
||||||
|
EpochSecondProvider epochSecondProvider
|
||||||
|
) {
|
||||||
|
this.pkInfoDao = pkInfoDao;
|
||||||
|
this.userDao = userDao;
|
||||||
|
this.coinRecordsDao = coinRecordsDao;
|
||||||
|
this.functionConfigProvider = functionConfigProvider;
|
||||||
|
this.epochSecondProvider = epochSecondProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public String pinToTop(int operatorUserId, int articleId, int pinExpireTime) {
|
||||||
|
long now = epochSecondProvider.nowEpochSecond();
|
||||||
|
ensureExpireTimeValid(pinExpireTime, now);
|
||||||
|
|
||||||
|
PkInfoModel pkInfoModel = pkInfoDao.selectById(articleId);
|
||||||
|
if (pkInfoModel == null) {
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "该信息不存在");
|
||||||
|
}
|
||||||
|
ensureOwner(operatorUserId, pkInfoModel.getSenderId());
|
||||||
|
|
||||||
|
int costPerHour = getPinCoinPerHour();
|
||||||
|
long hours = VVTools.calculateHoursRound(pinExpireTime, now);
|
||||||
|
int totalCoin = toIntCoin(multiplyCoin(costPerHour, hours, "置顶时长过长"), "置顶时长过长");
|
||||||
|
|
||||||
|
ensureDeductPoints(operatorUserId, totalCoin);
|
||||||
|
updatePinTime(pkInfoModel, pinExpireTime, (int) now);
|
||||||
|
insertCoinRecord("置顶扣除积分", operatorUserId, totalCoin, (int) now, COIN_RECORD_DEDUCT);
|
||||||
|
|
||||||
|
return String.format("置顶成功,扣除%d积分", totalCoin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public String cancelPin(int operatorUserId, int articleId) {
|
||||||
|
long now = epochSecondProvider.nowEpochSecond();
|
||||||
|
|
||||||
|
PkInfoModel pkInfoModel = pkInfoDao.selectById(articleId);
|
||||||
|
if (pkInfoModel == null) {
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "该信息不存在");
|
||||||
|
}
|
||||||
|
ensureOwner(operatorUserId, pkInfoModel.getSenderId());
|
||||||
|
|
||||||
|
int costPerHour = getPinCoinPerHour();
|
||||||
|
int pinExpireTime = pkInfoModel.getPinExpireTime() == null ? 0 : pkInfoModel.getPinExpireTime();
|
||||||
|
long hours = VVTools.calculateHoursFloor(pinExpireTime, now);
|
||||||
|
int refundCoin = toIntCoin(multiplyCoin(costPerHour, hours, "返还积分计算溢出"), "返还积分计算溢出");
|
||||||
|
|
||||||
|
if (refundCoin > 0) {
|
||||||
|
int updated = userDao.increasePoints(operatorUserId, refundCoin);
|
||||||
|
if (updated != 1) {
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "用户不存在");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pkInfoModel.setPinExpireTime(0);
|
||||||
|
pkInfoModel.setPinCreateTime(0);
|
||||||
|
if (pkInfoDao.updateById(pkInfoModel) != 1) {
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
insertCoinRecord("取消置顶返还积分", operatorUserId, refundCoin, (int) now, COIN_RECORD_ADD);
|
||||||
|
return String.format("操作成功,返还%d积分", refundCoin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ensureExpireTimeValid(int pinExpireTime, long now) {
|
||||||
|
if (pinExpireTime <= now) {
|
||||||
|
throw new BusinessException(ErrorCode.PARAMS_ERROR, "置顶到期时间必须大于当前时间");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ensureOwner(int operatorUserId, Integer senderId) {
|
||||||
|
if (senderId == null || senderId != operatorUserId) {
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "无权限操作");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getPinCoinPerHour() {
|
||||||
|
String value = functionConfigProvider.getValue(PIN_COIN_CONFIG_NAME);
|
||||||
|
if (value == null) {
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "未配置置顶扣除积分");
|
||||||
|
}
|
||||||
|
int coin;
|
||||||
|
try {
|
||||||
|
coin = Integer.parseInt(value);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "置顶扣除积分配置错误");
|
||||||
|
}
|
||||||
|
if (coin <= 0) {
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "置顶扣除积分配置错误");
|
||||||
|
}
|
||||||
|
return coin;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long multiplyCoin(int costPerHour, long hours, String overflowMessage) {
|
||||||
|
try {
|
||||||
|
return Math.multiplyExact((long) costPerHour, hours);
|
||||||
|
} catch (ArithmeticException e) {
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, overflowMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int toIntCoin(long coin, String overflowMessage) {
|
||||||
|
if (coin > Integer.MAX_VALUE) {
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, overflowMessage);
|
||||||
|
}
|
||||||
|
return (int) coin;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureDeductPoints(int operatorUserId, int totalCoin) {
|
||||||
|
int updated = userDao.decreasePointsIfEnough(operatorUserId, totalCoin);
|
||||||
|
if (updated == 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UserModel userModel = userDao.selectById(operatorUserId);
|
||||||
|
if (userModel == null) {
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "用户不存在");
|
||||||
|
}
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, String.format("积分不足,需要%d积分", totalCoin));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePinTime(PkInfoModel pkInfoModel, int pinExpireTime, int now) {
|
||||||
|
pkInfoModel.setPinExpireTime(pinExpireTime);
|
||||||
|
pkInfoModel.setPinCreateTime(now);
|
||||||
|
if (pkInfoDao.updateById(pkInfoModel) != 1) {
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertCoinRecord(String desc, int userId, int coin, int now, int type) {
|
||||||
|
coinRecordsDao.insert(new CoinRecords(desc, userId, coin, now, type));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package vvpkassistant.pk.service;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import vvpkassistant.CoinRecords.CoinRecordsDao;
|
||||||
|
import vvpkassistant.Tools.EpochSecondProvider;
|
||||||
|
import vvpkassistant.User.mapper.UserDao;
|
||||||
|
import vvpkassistant.User.model.UserModel;
|
||||||
|
import vvpkassistant.common.ErrorCode;
|
||||||
|
import vvpkassistant.config.FunctionConfigProvider;
|
||||||
|
import vvpkassistant.exception.BusinessException;
|
||||||
|
import vvpkassistant.pk.mapper.PkInfoDao;
|
||||||
|
import vvpkassistant.pk.model.PkInfoModel;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
class PkPinServiceImplTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFailWhenExpireTimeNotInFuture() {
|
||||||
|
PkPinServiceImpl service = newServiceWithNow(100L);
|
||||||
|
BusinessException ex = Assertions.assertThrows(BusinessException.class,
|
||||||
|
() -> service.pinToTop(1, 10, 100));
|
||||||
|
Assertions.assertEquals(ErrorCode.PARAMS_ERROR.getCode(), ex.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFailWhenPointsNotEnoughEvenIfLessThanOneHour() {
|
||||||
|
long now = 1_000L;
|
||||||
|
|
||||||
|
PkInfoDao pkInfoDao = mock(PkInfoDao.class);
|
||||||
|
UserDao userDao = mock(UserDao.class);
|
||||||
|
CoinRecordsDao coinRecordsDao = mock(CoinRecordsDao.class);
|
||||||
|
FunctionConfigProvider functionConfigProvider = mock(FunctionConfigProvider.class);
|
||||||
|
EpochSecondProvider epochSecondProvider = () -> now;
|
||||||
|
|
||||||
|
when(functionConfigProvider.getValue("置顶扣除积分")).thenReturn("10");
|
||||||
|
|
||||||
|
PkInfoModel pkInfoModel = new PkInfoModel();
|
||||||
|
pkInfoModel.setId(10);
|
||||||
|
pkInfoModel.setSenderId(1);
|
||||||
|
when(pkInfoDao.selectById(10)).thenReturn(pkInfoModel);
|
||||||
|
|
||||||
|
when(userDao.decreasePointsIfEnough(1, 10)).thenReturn(0);
|
||||||
|
UserModel userModel = new UserModel();
|
||||||
|
userModel.setId(1);
|
||||||
|
userModel.setPoints(9);
|
||||||
|
when(userDao.selectById(1)).thenReturn(userModel);
|
||||||
|
|
||||||
|
PkPinServiceImpl service = new PkPinServiceImpl(
|
||||||
|
pkInfoDao, userDao, coinRecordsDao, functionConfigProvider, epochSecondProvider
|
||||||
|
);
|
||||||
|
|
||||||
|
int pinExpireTime = (int) (now + 1);
|
||||||
|
BusinessException ex = Assertions.assertThrows(BusinessException.class,
|
||||||
|
() -> service.pinToTop(1, 10, pinExpireTime));
|
||||||
|
Assertions.assertTrue(ex.getMessage().contains("积分不足"));
|
||||||
|
|
||||||
|
verify(pkInfoDao, never()).updateById(any());
|
||||||
|
verify(coinRecordsDao, never()).insert(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSucceedAndChargeOneHourWhenJustOneSecond() {
|
||||||
|
long now = 2_000L;
|
||||||
|
|
||||||
|
PkInfoDao pkInfoDao = mock(PkInfoDao.class);
|
||||||
|
UserDao userDao = mock(UserDao.class);
|
||||||
|
CoinRecordsDao coinRecordsDao = mock(CoinRecordsDao.class);
|
||||||
|
FunctionConfigProvider functionConfigProvider = mock(FunctionConfigProvider.class);
|
||||||
|
EpochSecondProvider epochSecondProvider = () -> now;
|
||||||
|
|
||||||
|
when(functionConfigProvider.getValue("置顶扣除积分")).thenReturn("10");
|
||||||
|
|
||||||
|
PkInfoModel pkInfoModel = new PkInfoModel();
|
||||||
|
pkInfoModel.setId(10);
|
||||||
|
pkInfoModel.setSenderId(1);
|
||||||
|
when(pkInfoDao.selectById(10)).thenReturn(pkInfoModel);
|
||||||
|
|
||||||
|
when(userDao.decreasePointsIfEnough(1, 10)).thenReturn(1);
|
||||||
|
when(pkInfoDao.updateById(any())).thenReturn(1);
|
||||||
|
when(coinRecordsDao.insert(any())).thenReturn(1);
|
||||||
|
|
||||||
|
PkPinServiceImpl service = new PkPinServiceImpl(
|
||||||
|
pkInfoDao, userDao, coinRecordsDao, functionConfigProvider, epochSecondProvider
|
||||||
|
);
|
||||||
|
|
||||||
|
int pinExpireTime = (int) (now + 1);
|
||||||
|
String msg = service.pinToTop(1, 10, pinExpireTime);
|
||||||
|
Assertions.assertTrue(msg.contains("扣除10积分"));
|
||||||
|
|
||||||
|
ArgumentCaptor<PkInfoModel> captor = ArgumentCaptor.forClass(PkInfoModel.class);
|
||||||
|
verify(pkInfoDao).updateById(captor.capture());
|
||||||
|
Assertions.assertEquals(Integer.valueOf(pinExpireTime), captor.getValue().getPinExpireTime());
|
||||||
|
Assertions.assertEquals(Integer.valueOf((int) now), captor.getValue().getPinCreateTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PkPinServiceImpl newServiceWithNow(long now) {
|
||||||
|
PkInfoDao pkInfoDao = mock(PkInfoDao.class);
|
||||||
|
UserDao userDao = mock(UserDao.class);
|
||||||
|
CoinRecordsDao coinRecordsDao = mock(CoinRecordsDao.class);
|
||||||
|
FunctionConfigProvider functionConfigProvider = mock(FunctionConfigProvider.class);
|
||||||
|
EpochSecondProvider epochSecondProvider = () -> now;
|
||||||
|
return new PkPinServiceImpl(pkInfoDao, userDao, coinRecordsDao, functionConfigProvider, epochSecondProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user