Merge remote-tracking branch 'origin/dev/dev' into dev/dev

This commit is contained in:
shahaibo
2024-04-02 10:15:49 +08:00
16 changed files with 155 additions and 239 deletions

View File

@@ -1,159 +0,0 @@
package com.ai.da.service.impl;
import com.ai.da.common.config.PayPalClient;
import com.ai.da.common.constant.PayPalCheckoutConstant;
import com.ai.da.common.utils.paypalRequest.WebhookVerifyRequest;
import com.ai.da.service.CallBackService;
import com.ai.da.service.PayPalCheckoutService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.paypal.api.payments.Event;
import com.paypal.base.Constants;
import com.paypal.base.SDKUtil;
import com.paypal.base.rest.APIContext;
import com.paypal.base.rest.PayPalRESTException;
import com.paypal.http.HttpResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import static com.ai.da.common.constant.PayPalCheckoutConstant.MODE;
// #Validate Webhook Sample
//
// This sample code demonstrates how to validate a webhook received on your
// web server. This sample assumes that you use the java servlet, which returns
// the HttpServletRequest object. However, you can modify this code to
// your specific case.
//
@Slf4j
@Service
public class CallBackServiceImpl implements CallBackService {
@Value("${paypal.client-id}")
private String clientId;
@Value("${paypal.client-secret}")
private String clientSecret;
@Resource
private PayPalClient payPalClient;
@Resource
private PayPalCheckoutService payPalCheckoutService;
@Override
public Boolean doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
return doPost(req, resp);
}
// ##Validate Webhook
protected Boolean doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
try {
String body = getBody(req);
Map webhookEvent = new ObjectMapper().readValue(body, Map.class);
HashMap<String, Object> webhookRequest = new HashMap<>();
webhookRequest.put("auth_algo",SDKUtil.validateAndGet(getHeadersInfo(req), "PAYPAL-AUTH-ALGO"));
webhookRequest.put("cert_url",SDKUtil.validateAndGet(getHeadersInfo(req), "PAYPAL-CERT-URL"));
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_event",webhookEvent);
WebhookVerifyRequest webhookVerifyRequest = new WebhookVerifyRequest();
webhookVerifyRequest.authorization(payPalCheckoutService.getOAuth());
webhookVerifyRequest.requestBody(webhookRequest);
// 验签
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);
// Set the webhookId that you received when you created this webhook.
apiContext.addConfiguration(Constants.PAYPAL_WEBHOOK_ID, PayPalCheckoutConstant.WEBHOOK_ID);
Boolean result = Event.validateReceivedEvent(apiContext, getHeadersInfo(
req), body);
log.info("Webhook Validated: " + result);
if (result){
// 处理订单数据
LinkedHashMap<String,LinkedHashMap<String,String>> webhookEventMap = (LinkedHashMap<String,LinkedHashMap<String,String>>) webhookEvent;
String orderId = webhookEventMap.get("resource").get("id");
payPalCheckoutService.processOrder(orderId);
return Boolean.TRUE;
}
}
} catch (PayPalRESTException | InvalidKeyException | NoSuchAlgorithmException | SignatureException e) {
log.error(e.getMessage());
}
return Boolean.FALSE;
}
// Simple helper method to help you extract the headers from HttpServletRequest object.
private static Map<String, String> getHeadersInfo(HttpServletRequest request) {
Map<String, String> map = new HashMap<String, String>();
@SuppressWarnings("rawtypes")
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String key = (String) headerNames.nextElement();
String value = request.getHeader(key);
map.put(key, value);
}
return map;
}
// Simple helper method to fetch request data as a string from HttpServletRequest object.
private static String getBody(HttpServletRequest request) throws IOException {
String body;
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
stringBuilder.append("");
}
} catch (IOException ex) {
throw ex;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
throw ex;
}
}
}
body = stringBuilder.toString();
log.info("回调参数 ===> {}", body);
return body;
}
}

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));