1、完善积分充值

2、升级积分扣除机制
3、优化部分代码
This commit is contained in:
2024-03-28 14:43:36 +08:00
parent 737ec594fd
commit 7d967ed41e
12 changed files with 142 additions and 59 deletions

View File

@@ -10,14 +10,14 @@ public class MQConfig {
public static final String GENERATE_EXCHANGE_FANOUT = "generate-exchange";
// public static final String GENERATE_QUEUE = "generate-queue-prod";
// public static final String GENERATE_QUEUE = "generate-queue-test";
public static final String GENERATE_QUEUE = "generate-queue-dev";
// public static final String GENERATE_QUEUE = "generate-queue";
// public static final String GENERATE_QUEUE = "generate-queue-dev";
public static final String GENERATE_QUEUE = "generate-queue-local";
public static final String SR_QUEUE = "SR-queue-dev";
// public static final String SR_QUEUE = "SR-queue-local";
// public static final String SR_QUEUE = "SR-queue-dev";
public static final String SR_QUEUE = "SR-queue-local";
// public static final String SR_RESULT_QUEUE = "SuperResolution-local";
public static final String SR_RESULT_QUEUE = "SuperResolution-dev";
public static final String SR_RESULT_QUEUE = "SuperResolution-local";
// public static final String SR_RESULT_QUEUE = "SuperResolution-dev";
public MQConfig() {
}

View File

@@ -0,0 +1,10 @@
package com.ai.da.common.constant;
public class CommonConstant {
// 单位 秒 一天过期
public static final Long TASK_EXPIRE_TIME = 24 * 60 * 60L;
// 单位 秒 两天过期
public static final Long CREDITS_EXPIRE_TIME = 2 * 24 * 60 * 60L;
// 单位 分钟
public static final Integer MINIO_IMAGE_EXPIRE_TIME = 24 * 60;
}

View File

@@ -2,9 +2,6 @@ package com.ai.da.common.constant;
public class PayPalCheckoutConstant {
// public static String MODE = "sandbox";
public static String MODE = "live";
public static final String CAPTURE = "CAPTURE";
/**
* 该标签将覆盖PayPal网站上PayPal帐户中的公司名称
@@ -168,12 +165,6 @@ public class PayPalCheckoutConstant {
public final static String CMD_NOTIFY_VALIDATE = "_notify-validate";
// public final static String WEBHOOK_ID = "31797347YC028794L";
// kim-sandbox
// public final static String WEBHOOK_ID = "1WH327112B602422N";
// kim-live
public final static String WEBHOOK_ID = "41L14847MC833625B";
public final static String PAYPAL_TOKEN_KEY = "PayPalAccessToken";

View File

@@ -9,7 +9,7 @@ public enum CreditsEventsEnum {
PRICE("price","1"),
// 6USD -> 1000 points ==> 10HKD -> 215 points ==> 2HKD -> 43points
BUY_CREDITS("Buy Credits","43"),
BUY_CREDITS("Buy Credits","50"),
INIT("init", "1000"),

View File

@@ -136,6 +136,10 @@ public class RedisUtil {
}
//- - - - - - - - - - - - - - - - - - - - - String类型 - - - - - - - - - - - - - - - - - - - -
public void addToString(String key, String value){
redisTemplate.opsForValue().set(key,value);
}
public void addToString(String key, String value, Long expiresIn){
redisTemplate.opsForValue().set(key,value,expiresIn, TimeUnit.SECONDS);
}
@@ -156,4 +160,8 @@ public class RedisUtil {
return redisTemplate.getExpire(key);
}
public void removeFromString(String key){
redisTemplate.delete(key);
}
}

View File

@@ -1,7 +1,6 @@
package com.ai.da.controller;
import com.ai.da.common.response.Response;
import com.ai.da.service.CallBackService;
import com.ai.da.service.PayPalCheckoutService;
import com.paypal.http.HttpResponse;
import com.paypal.http.exceptions.SerializeException;
@@ -61,7 +60,6 @@ public class PayPalCheckoutController {
}else {
return Response.fail("Request for refund failed.");
}
}
@ApiOperation("执行扣款")
@@ -70,8 +68,5 @@ public class PayPalCheckoutController {
Order response = payPalCheckoutService.captureOrder(orderNo);
return Response.success(response);
}
}

View File

@@ -10,4 +10,6 @@ public class Product extends BaseEntity{
private String title; //商品名称
private Integer price; //价格(分)
private Integer credits; // 积分
}

View File

@@ -26,4 +26,8 @@ public interface CreditsService extends IService<CreditsDetail> {
PageBaseResponse<CreditsDetail> queryCreditsDetailsPage(QueryIncomeOrExpenditureDTO queryPageByTimeDTO);
Boolean checkCredits(Long accountId, CreditsEventsEnum event, Integer num);
Boolean creditsPreDeduction(CreditsEventsEnum event, Integer num);
void taskCreditsDeduction(Long accountId, String taskId);
}

View File

@@ -4,6 +4,7 @@ import com.ai.da.common.config.exception.BusinessException;
import com.ai.da.common.context.UserContext;
import com.ai.da.common.enums.CreditsEventsEnum;
import com.ai.da.common.response.PageBaseResponse;
import com.ai.da.common.utils.RedisUtil;
import com.ai.da.mapper.primary.AccountMapper;
import com.ai.da.mapper.primary.CreditsDetailMapper;
import com.ai.da.mapper.primary.entity.Account;
@@ -15,23 +16,31 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.netty.util.internal.StringUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@Service
public class CreditsServiceImpl extends ServiceImpl<CreditsDetailMapper, CreditsDetail> implements CreditsService {
@Resource
private AccountService accountService;
@Value("${redis.key.credits.pre-deduction}")
private String creditsDeduction;
@Resource
private AccountService accountService;
@Resource
private AccountMapper accountMapper;
@Resource
private RedisUtil redisUtil;
@Override
public void initCredits() {
@@ -116,20 +125,20 @@ public class CreditsServiceImpl extends ServiceImpl<CreditsDetailMapper, Credits
public void insertToCreditsDetail(Long accountId, String changeEvent, String credits, String changeType) {
CreditsDetail creditsDetail = new CreditsDetail();
Account account = accountMapper.selectById(accountId);
// BigDecimal finalCredits;
BigDecimal finalCredits;
String changeCredits;
if ("positive".equals(changeType)) {
// finalCredits = account.getCredits().add(new BigDecimal(credits));
finalCredits = account.getCredits().add(new BigDecimal(credits));
changeCredits = "+" + credits;
} else {
// finalCredits = account.getCredits().subtract(new BigDecimal(credits));
finalCredits = account.getCredits().subtract(new BigDecimal(credits));
changeCredits = "-" + credits;
}
creditsDetail.setAccountId(accountId);
creditsDetail.setChangeEvent(changeEvent);
creditsDetail.setChangedCredits(changeCredits);
// creditsDetail.setCredits(finalCredits);
creditsDetail.setCredits(account.getCredits());
creditsDetail.setCredits(finalCredits);
// creditsDetail.setCredits(account.getCredits());
creditsDetail.setCreateTime(LocalDateTime.now());
baseMapper.insert(creditsDetail);
@@ -179,4 +188,59 @@ public class CreditsServiceImpl extends ServiceImpl<CreditsDetailMapper, Credits
}
return Boolean.TRUE;
}
/*
* 积分扣除升级 -- 预扣积分
* 思路:
* 1、先判断当前积分扣除待扣积分后剩余积分够不够当前操作需要的积分
* 2、将需要进行积分扣除操作请求的 任务id和需要扣除的积分存到redis
* 3、执行成功后从redis中拿出当前任务id对应需要扣除的积分扣除积分更新数据库积分移除redis的记录
* 4、执行失败直接移除记录
*/
/** 积分预扣除 */
public Boolean creditsPreDeduction(CreditsEventsEnum event, Integer num){
Long accountId = UserContext.getUserHolder().getId();
// 1、获取当前需要预扣除的积分
Set<String> keys = redisUtil.getKeysFromString(creditsDeduction + ":" + accountId + ":*");
List<String> multiValue = redisUtil.getMultiValue(keys);
int sum = multiValue.stream().mapToInt(Integer::parseInt).sum();
sum += Integer.parseInt(event.getValue()) * num;
// 2、获取当前积分
BigDecimal existingCredits = accountMapper.selectById(accountId).getCredits();
BigDecimal subtract = existingCredits.subtract(new BigDecimal(sum));
// 3、判断剩余积分是否够本次操作
if (subtract.compareTo(BigDecimal.ZERO) < 0){
// 3.1 不够,直接返回余额不够,充值
return Boolean.FALSE;
}
return Boolean.TRUE;
}
/** 执行扣除积分,更新数据库 */
@Override
@Transactional(rollbackFor = Exception.class)
public void taskCreditsDeduction(Long accountId, String taskId){
String key = creditsDeduction + ":" + accountId + ":" + taskId;
// 1、获取当前任务id对应的积分
String value = redisUtil.getFromString(key);
// 1.1 没有。返回,报错,未找到当前任务
if (StringUtil.isNullOrEmpty(value)){
throw new BusinessException("当前任务不存在,无法扣除积分");
}
// 2、操作数据库扣除积分
BigDecimal existingCredits = accountMapper.selectById(accountId).getCredits();
BigDecimal subtract = existingCredits.subtract(new BigDecimal(value));
accountService.updateCredits(accountId, subtract.toString());
// 3、从redis中移除当前待扣积分
redisUtil.removeFromString(key);
}
}

View File

@@ -62,6 +62,12 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService {
@Value("${paypal.client-secret}")
private String clientSecret;
@Value("${paypal.mode}")
private String mode;
@Value("${paypal.webhook_id}")
private String webhookId;
@Resource
private PayPalClient payPalClient;
@Resource
@@ -90,7 +96,7 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService {
request.requestBody(buildRequestBody(String.valueOf(orderInfo.getTotalFee()), returnUrl));
HttpResponse<Order> response = null;
try {
response = payPalClient.client(MODE, clientId, clientSecret).execute(request);
response = payPalClient.client(mode, clientId, clientSecret).execute(request);
} catch (Exception e) {
log.error("调用paypal订单创建失败失败原因 ===> {}", e.getMessage());
throw new BusinessException("Order creation failed");
@@ -136,21 +142,21 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService {
webhookRequest.put("transmission_id", SDKUtil.validateAndGet(getHeadersInfo(req), "PAYPAL-TRANSMISSION-ID"));
webhookRequest.put("transmission_sig", SDKUtil.validateAndGet(getHeadersInfo(req), "PAYPAL-TRANSMISSION-SIG"));
webhookRequest.put("transmission_time", SDKUtil.validateAndGet(getHeadersInfo(req), "PAYPAL-TRANSMISSION-TIME"));
webhookRequest.put("webhook_id", PayPalCheckoutConstant.WEBHOOK_ID);
webhookRequest.put("webhook_id", webhookId);
webhookRequest.put("webhook_event", webhookEvent);
WebhookVerifyRequest webhookVerifyRequest = new WebhookVerifyRequest();
webhookVerifyRequest.authorization(getOAuth());
webhookVerifyRequest.requestBody(webhookRequest);
// 验签
HttpResponse<HashMap> verified = payPalClient.client(MODE, clientId, clientSecret).execute(webhookVerifyRequest);
HttpResponse<HashMap> verified = payPalClient.client(mode, clientId, clientSecret).execute(webhookVerifyRequest);
boolean verifyResult = verified.result().get("verification_status").toString().equals("SUCCESS");
if (verifyResult) {
// ### Api Context
APIContext apiContext = new APIContext(clientId, clientSecret, PayPalCheckoutConstant.MODE);
APIContext apiContext = new APIContext(clientId, clientSecret, mode);
// Set the webhookId that you received when you created this webhook.
apiContext.addConfiguration(Constants.PAYPAL_WEBHOOK_ID, PayPalCheckoutConstant.WEBHOOK_ID);
apiContext.addConfiguration(Constants.PAYPAL_WEBHOOK_ID, webhookId);
Boolean result = Event.validateReceivedEvent(apiContext, getHeadersInfo(
req), body);
log.info("Webhook Validated: " + result);
@@ -328,7 +334,7 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService {
HttpResponse<Order> response = null;
try {
response = payPalClient.client(MODE, clientId, clientSecret).execute(request);
response = payPalClient.client(mode, clientId, clientSecret).execute(request);
} catch (Exception e) {
log.error("paypal订单查询失败失败原因 ===> {}", e.getMessage());
}
@@ -371,7 +377,7 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService {
HttpResponse<Order> response;
try {
response = payPalClient.client(MODE, clientId, clientSecret).execute(request);
response = payPalClient.client(mode, clientId, clientSecret).execute(request);
} catch (Exception e) {
log.error("调用paypal扣款失败失败原因 ===> {}", e.getMessage());
throw new BusinessException("Order deduction failed.");
@@ -421,7 +427,7 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService {
public String queryCapture(String orderNo) throws IOException {
CapturesGetRequest request = new CapturesGetRequest("扣款id CaptureOrder生成");
HttpResponse<com.paypal.payments.Capture> response = payPalClient.client(MODE, clientId, clientSecret).execute(request);
HttpResponse<com.paypal.payments.Capture> response = payPalClient.client(mode, clientId, clientSecret).execute(request);
System.out.println("Status Code: " + response.statusCode());
System.out.println("Status: " + response.result().status());
System.out.println("Capture ids: " + response.result().id());
@@ -449,7 +455,7 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService {
ordersGetRequest.authorization("Bearer " + getOAuth());
boolean result;
try {
ordersGetResponse = payPalClient.client(MODE, clientId, clientSecret).execute(ordersGetRequest);
ordersGetResponse = payPalClient.client(mode, clientId, clientSecret).execute(ordersGetRequest);
} catch (Exception e) {
log.error("调用paypal订单查询失败失败原因 ===> {}", e.getMessage());
throw new BusinessException("Order query failed");
@@ -463,7 +469,7 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService {
request.requestBody(buildRefundRequestBody(String.valueOf(orderInfo.getTotalFee()), reason));
HttpResponse<com.paypal.payments.Refund> response = null;
try {
response = payPalClient.client(MODE, clientId, clientSecret).execute(request);
response = payPalClient.client(mode, clientId, clientSecret).execute(request);
} catch (IOException e) {
log.error("调用paypal退款申请失败失败原因 {}", e.getMessage());
throw new BusinessException("Request for refund failed");
@@ -529,7 +535,7 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService {
*/
public String queryRefund(String orderNo) throws IOException {
RefundsGetRequest request = new RefundsGetRequest("退款id RefundOrder生成");
HttpResponse<com.paypal.payments.Refund> response = payPalClient.client(MODE, clientId, clientSecret).execute(request);
HttpResponse<com.paypal.payments.Refund> response = payPalClient.client(mode, clientId, clientSecret).execute(request);
System.out.println("Status Code: " + response.statusCode());
System.out.println("Status: " + response.result().status());
System.out.println("Refund Id: " + response.result().id());
@@ -554,7 +560,7 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService {
AuthenticationRequest authenticationRequest = new AuthenticationRequest();
authenticationRequest.authorization(clientId, clientSecret);
try {
HttpResponse<HashMap> authResult = payPalClient.client(MODE, clientId, clientSecret).execute(authenticationRequest);
HttpResponse<HashMap> authResult = payPalClient.client(mode, clientId, clientSecret).execute(authenticationRequest);
String accessToken = authResult.result().get("access_token").toString();
long expiresIn = Long.parseLong(authResult.result().get("expires_in").toString());
// 3、存redis

View File

@@ -1,6 +1,7 @@
package com.ai.da.service.impl;
import com.ai.da.common.config.exception.BusinessException;
import com.ai.da.common.constant.CommonConstant;
import com.ai.da.common.context.UserContext;
import com.ai.da.common.enums.CreditsEventsEnum;
import com.ai.da.common.response.PageBaseResponse;
@@ -65,25 +66,24 @@ public class SuperResolutionServiceImpl extends ServiceImpl<TaskListMapper, Task
@Value("${minio.endpoint}")
private String endpoint;
@Value("${redis.key.credits.pre-deduction}")
private String creditsDeduction;
@Override
@Transactional(rollbackFor = Exception.class)
public List<String> prepareForSR(List<SuperResolutionDTO> superResolutionDTOList) {
Long accountId = UserContext.getUserHolder().getId();
// 1、判断用户当前积分是否够本次超分消耗
Boolean credits = creditsService.checkCredits(accountId, CreditsEventsEnum.SUPER_RESOLUTION, superResolutionDTOList.size());
// todo 积分扣除待升级
if (credits) {
// 先扣除积分,后失败后再加上
creditsService.creditsDecrease(accountId, CreditsEventsEnum.SUPER_RESOLUTION.getName());
} else {
Boolean preDeduction = creditsService.creditsPreDeduction(CreditsEventsEnum.SUPER_RESOLUTION, superResolutionDTOList.size());
if (!preDeduction) {
throw new BusinessException("Not enough Credits");
}
ArrayList<String> uuidList = new ArrayList<>();
for (SuperResolutionDTO superResolutionDTO : superResolutionDTOList) {
// todo 校验倍率是否是2的幂次(前端已做)
// 2、生成唯一id 使用uuid
String uuid = UUID.randomUUID().toString();
int num = 1;
@@ -119,17 +119,20 @@ public class SuperResolutionServiceImpl extends ServiceImpl<TaskListMapper, Task
baseMapper.insert(taskList);
// 5、加入任务列表 设置状态为 等待中
// 5、添加当前任务的预扣积分到redis 任务有效期一天,若待扣积分两天还没被移除,说明任务已经失败,待扣积分自动失效
redisUtil.addToString(creditsDeduction + ":" + accountId + ":" + uuid, CreditsEventsEnum.SUPER_RESOLUTION.getValue(), CommonConstant.CREDITS_EXPIRE_TIME);
// 6、加入任务列表 设置状态为 等待中
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String name = superResolutionDTO.getImages();
taskListService.addToTaskListRedis(new TaskDTO<>(uuid, "SR", name.substring(name.lastIndexOf("/") + 1), superResolutionDTO, "Waiting", LocalDateTime.now().format(dateTimeFormatter)));
// 6、将消息发布到MQ消息队列
// 7、将消息发布到MQ消息队列
log.info("发送消息到SR_QUEUE参数 {}", jsonString);
rabbitMQService.publishMessageToSR(jsonString);
}
// 6、返回唯一id列表
// 8、返回唯一id列表
return uuidList;
}
@@ -156,9 +159,8 @@ public class SuperResolutionServiceImpl extends ServiceImpl<TaskListMapper, Task
CreditsEventsEnum.SUPER_RESOLUTION.getName(),
CreditsEventsEnum.SUPER_RESOLUTION.getValue(),
"negative");
} else {
// 将之前扣除的积分返还
creditsService.creditsIncrease(accountId, CreditsEventsEnum.SUPER_RESOLUTION.getName());
// 4、扣除积分
creditsService.taskCreditsDeduction(accountId, taskId);
}
}

View File

@@ -1,5 +1,6 @@
package com.ai.da.service.impl;
import com.ai.da.common.constant.CommonConstant;
import com.ai.da.common.context.UserContext;
import com.ai.da.common.response.PageBaseResponse;
import com.ai.da.common.utils.MinioUtil;
@@ -61,8 +62,8 @@ public class TaskListServiceImpl extends ServiceImpl<TaskListMapper, TaskList> i
taskDTOS.add(new TaskDTO<>());
} else {
SuperResolutionDTO inputParam = taskDTO.getInputParam();
inputParam.setImages(minioUtil.getPresignedUrl(inputParam.getImages(), 24 * 60));
taskDTO.setOutputImage(StringUtil.isNullOrEmpty(taskDTO.getOutputImage()) ? null : minioUtil.getPresignedUrl(taskDTO.getOutputImage(), 24 * 60));
inputParam.setImages(minioUtil.getPresignedUrl(inputParam.getImages(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
taskDTO.setOutputImage(StringUtil.isNullOrEmpty(taskDTO.getOutputImage()) ? null : minioUtil.getPresignedUrl(taskDTO.getOutputImage(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
taskDTOS.add(taskDTO);
}
});
@@ -71,7 +72,7 @@ public class TaskListServiceImpl extends ServiceImpl<TaskListMapper, TaskList> i
public void addToTaskListRedis(TaskDTO<SuperResolutionDTO> taskDTO) {
String key = taskListKey + ":" + UserContext.getUserHolder().getId() + ":" + taskDTO.getTaskId();
redisUtil.addToString(key, new Gson().toJson(taskDTO), 24L * 60 * 60 * 3);
redisUtil.addToString(key, new Gson().toJson(taskDTO), CommonConstant.TASK_EXPIRE_TIME);
}
// 3、更新任务状态
@@ -104,8 +105,8 @@ public class TaskListServiceImpl extends ServiceImpl<TaskListMapper, TaskList> i
// 成功失败的都返回
TaskVO task = new TaskVO();
task.setImageName(s.getInputUrl().substring(s.getInputUrl().lastIndexOf("/") + 1));
task.setInputImage(minioUtil.getPresignedUrl(s.getInputUrl(), 24 * 60));
task.setOutputImage(StringUtil.isNullOrEmpty(s.getOutputUrl()) ? null : minioUtil.getPresignedUrl(s.getOutputUrl(), 24 * 60));
task.setInputImage(minioUtil.getPresignedUrl(s.getInputUrl(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
task.setOutputImage(StringUtil.isNullOrEmpty(s.getOutputUrl()) ? null : minioUtil.getPresignedUrl(s.getOutputUrl(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
task.setStatus(s.getStatus());
task.setTaskId(s.getTaskId());
task.setCreateDate(s.getCreateTime().format(dateTimeFormatter));