diff --git a/pom.xml b/pom.xml index 87a9fc66..2c6dacea 100644 --- a/pom.xml +++ b/pom.xml @@ -164,7 +164,6 @@ 9.2.1.jre8 - org.springframework.boot @@ -184,6 +183,25 @@ 4.22.57.ALL + + + com.paypal.sdk + checkout-sdk + 1.0.5 + + + + com.paypal.sdk + rest-api-sdk + LATEST + + + + org.json + json + 20230618 + + diff --git a/src/main/java/com/ai/da/common/RabbitMQ/MQConsumer.java b/src/main/java/com/ai/da/common/RabbitMQ/GenerateConsumer.java similarity index 97% rename from src/main/java/com/ai/da/common/RabbitMQ/MQConsumer.java rename to src/main/java/com/ai/da/common/RabbitMQ/GenerateConsumer.java index 77f46dbd..58f9435a 100644 --- a/src/main/java/com/ai/da/common/RabbitMQ/MQConsumer.java +++ b/src/main/java/com/ai/da/common/RabbitMQ/GenerateConsumer.java @@ -22,7 +22,7 @@ import java.util.Objects; @Slf4j @Component -public class MQConsumer { +public class GenerateConsumer { @Resource private GenerateService generateService; @@ -30,13 +30,13 @@ public class MQConsumer { @Resource private RedisUtil redisUtil; - @Value("${redis.key.consumptionOrder}") + @Value("${redis.key.orderForGenerate}") private String consumptionOrderKey; - @Value("${redis.key.cancelSet}") + @Value("${redis.key.generateCancelSet}") private String cancelSetKey; - @Value("${redis.key.exceptionMap}") + @Value("${redis.key.generateExceptionMap}") private String exceptionMapKey; @Value("${redis.key.resultMap}") diff --git a/src/main/java/com/ai/da/common/RabbitMQ/MQConfig.java b/src/main/java/com/ai/da/common/RabbitMQ/MQConfig.java index bb4aa412..79ecda25 100644 --- a/src/main/java/com/ai/da/common/RabbitMQ/MQConfig.java +++ b/src/main/java/com/ai/da/common/RabbitMQ/MQConfig.java @@ -1,24 +1,23 @@ package com.ai.da.common.RabbitMQ; -import org.springframework.amqp.core.Binding; -import org.springframework.amqp.core.BindingBuilder; -import org.springframework.amqp.core.FanoutExchange; import org.springframework.amqp.core.Queue; -import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.core.RabbitAdmin; -import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.beans.factory.annotation.Value; @Configuration 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-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 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 MQConfig() { } @@ -32,10 +31,20 @@ public class MQConfig { * 创建队列,使用工作模式,不用定义交换机 */ @Bean - public Queue queueRasa() { + public Queue generateQueue() { return new Queue(GENERATE_QUEUE); } + @Bean + public Queue SRQueue() { + return new Queue(SR_QUEUE); + } + + @Bean + public Queue SRResultQueue() { + return new Queue(SR_RESULT_QUEUE); + } + /** * 将队列绑定到交换机上【队列订阅交换机】 */ diff --git a/src/main/java/com/ai/da/common/RabbitMQ/MQPublisher.java b/src/main/java/com/ai/da/common/RabbitMQ/MQPublisher.java index b0429110..8afffa29 100644 --- a/src/main/java/com/ai/da/common/RabbitMQ/MQPublisher.java +++ b/src/main/java/com/ai/da/common/RabbitMQ/MQPublisher.java @@ -18,7 +18,11 @@ public class MQPublisher { public void sendGenerateMessage(String mm) { log.info("send message:" + mm); amqpTemplate.convertAndSend(MQConfig.GENERATE_QUEUE, mm); + } + public void sendSRMessage(String mm) { + log.info("send message:" + mm); + amqpTemplate.convertAndSend(MQConfig.SR_QUEUE, mm); } } diff --git a/src/main/java/com/ai/da/common/RabbitMQ/SRConsumer.java b/src/main/java/com/ai/da/common/RabbitMQ/SRConsumer.java new file mode 100644 index 00000000..059b4f81 --- /dev/null +++ b/src/main/java/com/ai/da/common/RabbitMQ/SRConsumer.java @@ -0,0 +1,203 @@ +package com.ai.da.common.RabbitMQ; + +import com.ai.da.common.config.exception.BusinessException; +import com.ai.da.common.utils.RedisUtil; +import com.ai.da.model.dto.SuperResolutionDTO; +import com.ai.da.model.dto.TaskDTO; +import com.ai.da.service.SuperResolutionService; +import com.ai.da.service.TaskListService; +import com.alibaba.fastjson.JSONException; +import com.alibaba.fastjson.JSONObject; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.rabbitmq.client.Channel; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.rabbit.annotation.RabbitHandler; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.HashMap; + +@Slf4j +@Component +public class SRConsumer { + + @Resource + private RedisUtil redisUtil; + + @Resource + private TaskListService taskListService; + + @Value("${redis.key.orderForSR}") + private String consumptionOrderKey; + + @Value("${redis.key.SRCancelSet}") + private String cancelSetKey; + + @Value("${redis.key.SRExceptionMap}") + private String exceptionMapKey; + + @Value("${redis.key.taskList}") + private String taskListKey; + + @Resource + private SuperResolutionService superResolutionService; + + /** + * 请求超分处理 + */ + public void superResolution(Message msg, Channel channel, String consumerName) { + log.info("============SR start listening=========="); + long start = System.currentTimeMillis(); + + SuperResolutionDTO superResolutionDTO; + String uniqueId = null; + + try { + superResolutionDTO = JSONObject.parseObject(msg.getBody(), SuperResolutionDTO.class); + uniqueId = superResolutionDTO.getUniqueId(); + log.info("From " + consumerName + " : " + uniqueId); + superResolutionService.updateSROutput(uniqueId, "Executing", null); + taskListService.updateTaskStatusOrOutputRedis(uniqueId, "Executing", null); + /*try { + Thread.sleep(2 * 60 * 1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + }*/ + // 2、判断当前消息是否在取消列表中 + Boolean isMember = redisUtil.isElementExistsInSet(cancelSetKey, uniqueId); + if (isMember) { + try { + // 2.1 手动确认该消息 + channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false); + } catch (IOException ex) { + log.error("手动确认,不返回队列重新消费"); + } + } else { + // 请求python端进行超分 + superResolutionService.SR(superResolutionDTO); + } + } catch (BusinessException e) { + log.error(e.getMsg()); + superResolutionDTO = JSONObject.parseObject(msg.getBody(), SuperResolutionDTO.class); + // channel.basicNack() 为不确认deliveryTag对应的消息,第二个参数是否应用于多消息,第三个参数是否requeue + setErrorMessage(msg, channel, e.getMsg(), superResolutionDTO); + } catch (JSONException e) { + log.error(e.getMessage()); + setErrorMessage(msg, channel, e.getMessage(), null); + } catch (Exception e) { + log.error(e.getMessage()); + superResolutionDTO = JSONObject.parseObject(msg.getBody(), SuperResolutionDTO.class); + setErrorMessage(msg, channel, e.getMessage(), superResolutionDTO); + + } + + long end = System.currentTimeMillis(); + + log.info(" task_id: " + uniqueId + "----------" + consumerName + " 执行时长:" + (end - start) + "毫秒"); + log.info("=============SR end listening==========="); + } + + /** + * 获取超分结果 + */ + public void getSRResult(Message msg, Channel channel, String consumerName) { + log.info("============SRResult start listening=========="); + long start = System.currentTimeMillis(); + + JSONObject result = null; + String taskId = null; + + try { + result = JSONObject.parseObject(msg.getBody(), JSONObject.class); + taskId = result.get("tasks_id").toString(); + } catch (JSONException e) { + log.error("SRResult 返回数据格式不合规范"); + log.error(e.getMessage()); + setErrorMessage(msg, channel, e.getMessage(), null); + } + + + try { +// channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false); + + // 2、判断状态是否成功 + if ("SUCCESS".equals(result.get("status").toString())) { + String output = result.get("data").toString(); + superResolutionService.setSRResult(taskId, output, "success"); + taskListService.updateTaskStatusOrOutputRedis(taskId, "success", output); + } else { + superResolutionService.setSRResult(taskId, null, "fail"); + taskListService.updateTaskStatusOrOutputRedis(taskId, "fail", null); + HashMap exceptionInfo = new HashMap<>(); + // 获取输入信息 + String task = redisUtil.getFromString(taskListKey + taskId + taskId.substring(taskId.lastIndexOf("-") + 1)); + Gson gson = new Gson(); + Type type = new TypeToken>() { + }.getType(); + TaskDTO taskDTO = gson.fromJson(task, type); + // 将输入信息和报错信息均存入redis todo 加判空 + exceptionInfo.put(taskId, "Input ==> " + taskDTO.getInputParam() + "Fail Message ==> " + result.get("message").toString()); + // 将报错信息存入redis + redisUtil.addToMap(exceptionMapKey, exceptionInfo); + } + } catch (Exception e) { + log.error(e.getMessage()); + // channel.basicNack() 为不确认deliveryTag对应的消息,第二个参数是否应用于多消息,第三个参数是否requeue + try { + // 第二个参数,是否批量确认消息,当传false时,只确认当前 deliveryTag对应的消息;当传true时,会确认当前及之前所有未确认的消息。 + channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false); + } catch (IOException exception) { + log.error("手动确认,取消返回队列,不再重新消费"); + } + } + + long end = System.currentTimeMillis(); + + log.info(" task_id: " + taskId + "----------" + consumerName + " 执行时长:" + (end - start) + "毫秒"); + log.info("=============SRResult end listening==========="); + } + + private void setErrorMessage(Message msg, Channel channel, String message, SuperResolutionDTO superResolutionDTO) { + String uniqueId; + try { + // 第二个参数,是否批量确认消息,当传false时,只确认当前 deliveryTag对应的消息;当传true时,会确认当前及之前所有未确认的消息。 + channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false); + uniqueId = superResolutionDTO.getUniqueId(); + // 将消息从redis排队队列中删除,需保证被消费的消息存储到db之后再从redis删除 + redisUtil.removeFromZSet(consumptionOrderKey, uniqueId); + } catch (Exception exception) { + log.error("手动确认,取消返回队列,不再重新消费"); + throw new BusinessException("发生错误,手动确认消息"); + } + // 将入参和错误信息存入redis + String exceptionMessage = JSONObject.toJSONString(superResolutionDTO) + + " Exception message : " + message; +// " Exception message : " + e.getMessage(); + HashMap exceptionInfo = new HashMap<>(); + uniqueId = superResolutionDTO.getUniqueId(); + exceptionInfo.put(String.valueOf(uniqueId), exceptionMessage); + // 存redis + redisUtil.addToMap(exceptionMapKey, exceptionInfo); + taskListService.updateTaskStatusOrOutputRedis(uniqueId, "fail", null); + } + + @RabbitListener(queues = MQConfig.SR_QUEUE) + @RabbitHandler + public void SRConsumer1(Message msg, Channel channel) { + superResolution(msg, channel, "consumer 1"); + } + + + @RabbitListener(queues = MQConfig.SR_RESULT_QUEUE) + @RabbitHandler + public void SRResultConsumer1(Message msg, Channel channel) { + getSRResult(msg, channel, "consumer 1"); + } + +} diff --git a/src/main/java/com/ai/da/common/config/PayPalClient.java b/src/main/java/com/ai/da/common/config/PayPalClient.java new file mode 100644 index 00000000..bcdef0a3 --- /dev/null +++ b/src/main/java/com/ai/da/common/config/PayPalClient.java @@ -0,0 +1,51 @@ +package com.ai.da.common.config; + +import com.paypal.core.PayPalEnvironment; +import com.paypal.core.PayPalHttpClient; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.json.JSONArray; +import org.json.JSONObject; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +import java.util.Iterator; + +@Configuration +@Slf4j +@PropertySource("classpath:paypal-sandbox.properties") +public class PayPalClient { + + public PayPalHttpClient client(String mode, String clientId, String clientSecret) { + log.info("mode={}, clientId={}, clientSecret={}", mode, clientId, clientSecret); + PayPalEnvironment environment = mode.equals("live") ? new PayPalEnvironment.Live(clientId, clientSecret) : new PayPalEnvironment.Sandbox(clientId, clientSecret); + return new PayPalHttpClient(environment); + } + + /** + * @param jo + * @param pre + * @return + */ + /*public String prettyPrint(JSONObject jo, String pre) { + Iterator keys = jo.keys(); + StringBuilder pretty = new StringBuilder(); + while (keys.hasNext()) { + String key = (String) keys.next(); + pretty.append(String.format("%s%s: ", pre, StringUtils.capitalize(key))); + if (jo.get(key) instanceof JSONObject) { + pretty.append(prettyPrint(jo.getJSONObject(key), pre + "\t")); + } else if (jo.get(key) instanceof JSONArray) { + int sno = 1; + for (Object jsonObject : jo.getJSONArray(key)) { + pretty.append(String.format("\n%s\t%d:\n", pre, sno++)); + pretty.append(prettyPrint((JSONObject) jsonObject, pre + "\t\t")); + } + } else { + pretty.append(String.format("%s\n", jo.getString(key))); + } + } + return pretty.toString(); + }*/ +} + diff --git a/src/main/java/com/ai/da/common/constant/PayPalCheckoutConstant.java b/src/main/java/com/ai/da/common/constant/PayPalCheckoutConstant.java new file mode 100644 index 00000000..31cc4adf --- /dev/null +++ b/src/main/java/com/ai/da/common/constant/PayPalCheckoutConstant.java @@ -0,0 +1,182 @@ +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帐户中的公司名称 + */ + public static final String BRANDNAME = "AIDA"; + /** + * LOGIN。当客户单击PayPal Checkout时,客户将被重定向到页面以登录PayPal并批准付款。 + * BILLING。当客户单击PayPal Checkout时,客户将被重定向到一个页面,以输入信用卡或借记卡以及完成购买所需的其他相关账单信息 + * NO_PREFERENCE。当客户单击“ PayPal Checkout”时,将根据其先前的交互方式将其重定向到页面以登录PayPal并批准付款,或重定向至页面以输入信用卡或借记卡以及完成购买所需的其他相关账单信息使用PayPal。 + * 默认值:NO_PREFERENCE + */ + public static final String LANDINGPAGE = "NO_PREFERENCE"; + /** + * CONTINUE。将客户重定向到PayPal付款页面后,将出现“ 继续”按钮。当结帐流程启动时最终金额未知时,请使用此选项,并且您想将客户重定向到商家页面而不处理付款。 + * PAY_NOW。将客户重定向到PayPal付款页面后,出现“ 立即付款”按钮。当启动结帐时知道最终金额并且您要在客户单击“ 立即付款”时立即处理付款时,请使用此选项。 + */ + public static final String USERACTION = "PAY_NOW"; + /** + * GET_FROM_FILE。使用贝宝网站上客户提供的送货地址。 + * NO_SHIPPING。从PayPal网站编辑送货地址。推荐用于数字商品 + * SET_PROVIDED_ADDRESS。使用商家提供的地址。客户无法在PayPal网站上更改此地址 + */ +// public static final String SHIPPINGPREFERENCE = "SET_PROVIDED_ADDRESS"; + public static final String SHIPPINGPREFERENCE = "NO_SHIPPING"; + /** + * 交易异常 + */ + public static final String FAILURE = "failure"; + /** + * 交易成功 + */ + public static final String SUCCESS = "success"; + + /** + * ipn回调。支付成功 + */ + public static final String PAYMENT_STATUS_COMPLETED = "Completed"; + /** + * ipn回调。退款成功 + */ + public static final String PAYMENT_STATUS_REFUNDED = "Refunded"; + /** + * ipn回调。待定 + */ + public static final String PAYMENT_STATUS_PENDING = "Pending"; + + /** + * ipn回调,付款因退款或其他类型的冲销而被冲销。资金已从您的帐户余额中删除,并退还给买方 + */ + public static final String PAYMENT_STATUS_REVERSED = "Reversed"; + + /** + * ipn回调, 撤销已被取消。例如,您赢得了与客户的纠纷,并且撤回的交易资金已退还给您 + */ + public static final String PAYMENT_STATUS_CANCELED_REVERSAL = "Canceled_Reversal"; + + /** + * ipn回调,付款被拒绝 + */ + public static final String PAYMENT_STATUS_DENIED = "Denied"; + + /** + * ipn回调, 此授权已过期,无法捕获 + */ + public static final String PAYMENT_STATUS_EXPIRED = "Expired"; + + /** + * ipn回调, 德国的ELV付款是通过Express Checkout进行的 + */ + public static final String PAYMENT_STATUS_CREATED = "Created"; + + /** + * ipn回调, 付款失败。仅当付款是通过您客户的银行帐户进行的。 + */ + public static final String PAYMENT_STATUS_FAILED = "Failed"; + + /** + * ipn回调,付款已被接受 + */ + public static final String PAYMENT_STATUS_PROCESSED = "Processed"; + + /** + * ipn回调,此授权已失效 + */ + public static final String PAYMENT_STATUS_VOIDED = "Voided"; + + //订单状态 + /** + * 1、支付完成;捕获的付款的资金已记入收款人的PayPal帐户 + * 2、退款完成;该交易的资金已记入客户的帐户 + */ + public static final String STATE_COMPLETED = "COMPLETED"; + /** + * 部分退款;少于所捕获付款金额的金额已部分退还给付款人。 + */ + public static final String STATE_PARTIALLY_REFUNDED = "PARTIALLY_REFUNDED"; + + + /** + * 1、支付待定;捕获的付款资金尚未记入收款人的PayPal帐户。有关更多信息请参见status.details。 + * 2、退款待定;有关更多信息,请参见status_details.reason。 + */ + /** + * 支付待定: + * capture_status_details + * reason 枚举 + * 捕获的付款状态为PENDING或DENIED的原因。可能的值为: + * BUYER_COMPLAINT。付款人与贝宝(PayPal)对此捕获的付款提出了争议。 + * CHARGEBACK。响应于付款人与用于支付此已捕获付款的金融工具的发行人对此已捕获的付款提出异议,已收回的资金被撤回。 + * ECHECK。由尚未结清的电子支票支付的付款人。 + * INTERNATIONAL_WITHDRAWAL。访问您的在线帐户。在您的“帐户概览”中,接受并拒绝此笔付款。 + * OTHER。无法提供其他特定原因。有关此笔付款的更多信息,请在线访问您的帐户或联系PayPal。 + * PENDING_REVIEW。捕获的付款正在等待人工审核。 + *(手动收取)RECEIVING_PREFERENCE_MANDATES_MANUAL_ACTION。收款人尚未为其帐户设置适当的接收首选项。有关如何接受或拒绝此付款的更多信息,请在线访问您的帐户。通常在某些情况下提供此原因,例如,当所捕获付款的货币与收款人的主要持有货币不同时。 + * REFUNDED。收回的资金已退还。 + * TRANSACTION_APPROVED_AWAITING_FUNDING。付款人必须将这笔付款的资金汇出。通常,此代码适用于手动EFT。 + * UNILATERAL。收款人没有PayPal帐户。 + * VERIFICATION_REQUIRED。收款人的PayPal帐户未通过验证。 + */ + /** + * 退款待定 + * 退款具有“PENDING”或“FAILED”状态的原因。 可能的值为: + * ECHECK。客户的帐户通过尚未结清的eCheck进行注资。 + */ + public static final String STATE_PENDING = "PENDING"; + /** + * 退款;大于或等于此捕获的付款金额的金额已退还给付款人 + */ + public static final String STATE_REFUNDED = "REFUNDED"; + /** + * 支付拒绝 + */ + public static final String STATE_DENIED = "DENIED"; + /** + * 退款失败 + */ + public static final String STATE_FAILED = "FAILED"; + + /** + * 争议状态 + */ + public static final String BUYER_COMPLAINT = "BUYER_COMPLAINT"; + + /** + * 沙箱环境请求网关地址 + */ + public static final String SANDBOX = "https://api.sandbox.paypal.com"; + /** + * 生产环境请求网关地址 + */ + public static final String LIVE = "https://api.paypal.com"; + /** + * 添加物流信息请求路径 + */ + public static final String ADD_TRACK_URL = "/v1/shipping/trackers-batch"; + + /** + * 修改物流信息请求路径 + */ + public static final String UPDATE_TRACK_URL = "/v1/shipping/trackers/"; + + 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"; + + +} + + diff --git a/src/main/java/com/ai/da/common/enums/CreditsEventsEnum.java b/src/main/java/com/ai/da/common/enums/CreditsEventsEnum.java new file mode 100644 index 00000000..23566466 --- /dev/null +++ b/src/main/java/com/ai/da/common/enums/CreditsEventsEnum.java @@ -0,0 +1,31 @@ +package com.ai.da.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum CreditsEventsEnum { + + PRICE("price","1"), + // 6USD -> 1000 points ==> 10HKD -> 215 points ==> 2HKD -> 43points + BUY_CREDITS("Buy Credits","43"), + + INIT("init", "1000"), + + DAILY_CHECKIN("Daily Check-In", "50"), + + SOCIAL_MEDIA_SHARING("Social Media Sharing","50"), + +// SUPER_RESOLUTION("Super Resolution","300"), + SUPER_RESOLUTION("Super Resolution","30"), + + OTHER("Other","10"); + + private String name; + + /** + * 对应事件需要消耗or获得的积分 + */ + private String value; +} diff --git a/src/main/java/com/ai/da/common/enums/CurrencyCodesEnum.java b/src/main/java/com/ai/da/common/enums/CurrencyCodesEnum.java new file mode 100644 index 00000000..1f0225c8 --- /dev/null +++ b/src/main/java/com/ai/da/common/enums/CurrencyCodesEnum.java @@ -0,0 +1,40 @@ +package com.ai.da.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +public enum CurrencyCodesEnum { + + AUSTRALIAN_DOLLAR("AUD"), + BRAZILIAN_REAL("BRL"), + CANADIAN_DOLLAR("CAD"), + CHINESE_RENMENBI("CNY"), + CZECH_KORUNA("CZK"), + DANISH_KRONE("DKK"), + EURO("EUR"), + HONG_KONG_DOLLAR("HKD"), + HUNGARIAN_FORINT("HUF"), + ISRAELI_NEW_SHEKEL("ILS"), + JAPANESE_YEN("JPY"), + MALAYSIAN_RINGGIT("MYR"), + MEXICAN_PESO("MXN"), + NEW_TAIWAN_DOLLAR("TWD"), + NEW_ZEALAND_DOLLAR("NZD"), + NORWEGIAN_KRONE("NOK"), + PHILIPPINE_PESO("PHP"), + POLISH_ZLOTY("PLN"), + POUND_STERLING("GBP"), + RUSSIAN_RUBLE("RUB"), + SINGAPORE_DOLLAR("SGD"), + SWEDISH_KRONA("SEK"), + SWISS_FRANC("CHF"), + THAI_BAHT("THB"), + UNITED_STATES_DOLLAR("USD"); + + private String code; + + CurrencyCodesEnum(String code) { + this.code = code; + } +} diff --git a/src/main/java/com/ai/da/common/enums/OrderStatusEnum.java b/src/main/java/com/ai/da/common/enums/OrderStatusEnum.java index ede1e162..559ae38b 100644 --- a/src/main/java/com/ai/da/common/enums/OrderStatusEnum.java +++ b/src/main/java/com/ai/da/common/enums/OrderStatusEnum.java @@ -20,7 +20,7 @@ public enum OrderStatusEnum { /** * 已关闭 */ - CLOSED("超时已关闭"), + TIMEOUT_CLOSED("超时已关闭"), /** * 已取消 diff --git a/src/main/java/com/ai/da/common/enums/PayPalOrderStatusEnum.java b/src/main/java/com/ai/da/common/enums/PayPalOrderStatusEnum.java new file mode 100644 index 00000000..f35fd8f8 --- /dev/null +++ b/src/main/java/com/ai/da/common/enums/PayPalOrderStatusEnum.java @@ -0,0 +1,24 @@ +package com.ai.da.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum PayPalOrderStatusEnum { + + CREATED("CREATED"), + + SAVED("SAVED"), + + APPROVED("APPROVED"), + + VOIDED("VOIDED"), + + COMPLETED("COMPLETED"), + + PAYER_ACTION_REQUIRED("PAYER_ACTION_REQUIRED"); + + + private final String status; +} diff --git a/src/main/java/com/ai/da/common/enums/PayTypeEnum.java b/src/main/java/com/ai/da/common/enums/PayTypeEnum.java index cb2e89f8..70067578 100644 --- a/src/main/java/com/ai/da/common/enums/PayTypeEnum.java +++ b/src/main/java/com/ai/da/common/enums/PayTypeEnum.java @@ -11,11 +11,15 @@ public enum PayTypeEnum { */ WXPAY("微信"), - /** * 支付宝 */ - ALIPAY("支付宝"); + ALIPAY("支付宝"), + + /** + * PayPal + */ + PAYPAL("PayPal"); /** * 类型 diff --git a/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java b/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java index b669f4d0..8ae7a2a1 100644 --- a/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java +++ b/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java @@ -48,7 +48,7 @@ public class AuthenticationFilter extends OncePerRequestFilter { "/api/third/party/addNoLoginRequiredNew","/api/third/party/deleteNoLoginRequiredNew", "/api/third/party/existNoLoginRequired","/api/third/party/getRedirectUrl", // "/api/python/chatStream", - "/api/python/flush","/api/account/healthy","/api/ali-pay/trade/notify" + "/api/python/flush","/api/account/healthy","/api/ali-pay/trade/notify","/api/paypal/ipn/back" ); @Override diff --git a/src/main/java/com/ai/da/common/task/AliPayTask.java b/src/main/java/com/ai/da/common/task/AliPayTask.java index f95a3a10..64b0e81c 100644 --- a/src/main/java/com/ai/da/common/task/AliPayTask.java +++ b/src/main/java/com/ai/da/common/task/AliPayTask.java @@ -29,7 +29,7 @@ public class AliPayTask { log.info("orderConfirm 被执行......"); - List orderInfoList = orderInfoService.getNoPayOrderByDuration(1, PayTypeEnum.ALIPAY.getType()); + List orderInfoList = orderInfoService.getNoPayOrderByDuration(5, PayTypeEnum.ALIPAY.getType()); for (OrderInfo orderInfo : orderInfoList) { String orderNo = orderInfo.getOrderNo(); diff --git a/src/main/java/com/ai/da/common/utils/AsyncCallerUtil.java b/src/main/java/com/ai/da/common/utils/AsyncCallerUtil.java index 35039431..29801a6b 100644 --- a/src/main/java/com/ai/da/common/utils/AsyncCallerUtil.java +++ b/src/main/java/com/ai/da/common/utils/AsyncCallerUtil.java @@ -14,6 +14,7 @@ import java.util.concurrent.*; @Component public class AsyncCallerUtil { + // 存放状态 表示当前任务是否需要继续等待,默认持续等待 public static Map waitingStatus = new HashMap<>(); private static PythonService pythonService; @@ -58,7 +59,7 @@ public class AsyncCallerUtil { return null; } catch (InterruptedException | ExecutionException | BusinessException e) { // 处理异常 - log.error("发生错误 : " + e); + log.error("发生错误 : " + e, e); // 取消定时任务 assert timeoutTask != null; timeoutTask.cancel(true); diff --git a/src/main/java/com/ai/da/common/utils/RedisUtil.java b/src/main/java/com/ai/da/common/utils/RedisUtil.java index 5121e883..9fa37738 100644 --- a/src/main/java/com/ai/da/common/utils/RedisUtil.java +++ b/src/main/java/com/ai/da/common/utils/RedisUtil.java @@ -7,8 +7,10 @@ import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import javax.annotation.Resource; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; @Slf4j @Component @@ -17,6 +19,10 @@ public class RedisUtil { @Resource private RedisTemplate redisTemplate; + public Boolean hasKey(String key){ + return redisTemplate.hasKey(key); + } + //- - - - - - - - - - - - - - - - - - - - - ZSet类型 - - - - - - - - - - - - - - - - - - - - /** @@ -64,10 +70,15 @@ public class RedisUtil { /** * 获取当前ZSet中数据量的总和 */ - public Long getZSetTotal(String key) { + public Long getZSetTotalCount(String key) { return redisTemplate.opsForZSet().zCard(key); } + + public Set getZSetTotalData(String key){ + return redisTemplate.opsForZSet().range(key, 0, -1); + } + //- - - - - - - - - - - - - - - - - - - - - set类型 - - - - - - - - - - - - - - - - - - - - /** @@ -123,4 +134,26 @@ public class RedisUtil { public Long removeFromMap(String key, String hashKeys) { return redisTemplate.opsForHash().delete(key, hashKeys); } + + //- - - - - - - - - - - - - - - - - - - - - String类型 - - - - - - - - - - - - - - - - - - - - + public void addToString(String key, String value, Long expiresIn){ + redisTemplate.opsForValue().set(key,value,expiresIn, TimeUnit.SECONDS); + } + + public String getFromString(String key){ + return redisTemplate.opsForValue().get(key); + } + + public Set getKeysFromString(String key){ + return redisTemplate.keys(key); + } + + public List getMultiValue(Set keys){ + return redisTemplate.opsForValue().multiGet(keys); + } + + public Long getExpire(String key){ + return redisTemplate.getExpire(key); + } + } diff --git a/src/main/java/com/ai/da/common/utils/paypalRequest/AuthenticationRequest.java b/src/main/java/com/ai/da/common/utils/paypalRequest/AuthenticationRequest.java new file mode 100644 index 00000000..eedda337 --- /dev/null +++ b/src/main/java/com/ai/da/common/utils/paypalRequest/AuthenticationRequest.java @@ -0,0 +1,30 @@ +package com.ai.da.common.utils.paypalRequest; + +import com.alibaba.fastjson.JSONObject; +import com.paypal.http.HttpRequest; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.HashMap; + +@Component +public class AuthenticationRequest extends HttpRequest { + + public AuthenticationRequest() { + super("/v1/oauth2/token", "POST", HashMap.class); + this.header("Content-Type", "application/x-www-form-urlencoded"); + body(); + } + + public AuthenticationRequest authorization(String clientId, String clientSecret) { + this.header(clientId, clientSecret); + return this; + } + + public AuthenticationRequest body() { + HashMap body = new HashMap<>(); + body.put("grant_type", "client_credentials"); + this.requestBody(body); + return this; + } +} diff --git a/src/main/java/com/ai/da/common/utils/paypalRequest/WebhookVerifyRequest.java b/src/main/java/com/ai/da/common/utils/paypalRequest/WebhookVerifyRequest.java new file mode 100644 index 00000000..957482d3 --- /dev/null +++ b/src/main/java/com/ai/da/common/utils/paypalRequest/WebhookVerifyRequest.java @@ -0,0 +1,26 @@ +package com.ai.da.common.utils.paypalRequest; + +import com.ai.da.model.dto.WebhookVerifyDTO; +import com.alibaba.fastjson.JSONObject; +import com.paypal.http.HttpRequest; +import com.paypal.orders.OrdersCreateRequest; + +import java.io.Serializable; +import java.util.HashMap; + +public class WebhookVerifyRequest extends HttpRequest { + public WebhookVerifyRequest() { + super("/v1/notifications/verify-webhook-signature", "POST", HashMap.class); + this.header("Content-Type", "application/json"); + } + + public WebhookVerifyRequest authorization(String authorization) { + this.header("Authorization", "Bearer " + String.valueOf(authorization)); + return this; + } + + public WebhookVerifyRequest requestBody(HashMap webhookVerify) { + super.requestBody(webhookVerify); + return this; + } +} diff --git a/src/main/java/com/ai/da/controller/AliPayController.java b/src/main/java/com/ai/da/controller/AliPayController.java index 5559a186..b3a9c65b 100644 --- a/src/main/java/com/ai/da/controller/AliPayController.java +++ b/src/main/java/com/ai/da/controller/AliPayController.java @@ -1,20 +1,13 @@ package com.ai.da.controller; import com.ai.da.common.response.Response; -import com.ai.da.mapper.primary.entity.OrderInfo; import com.ai.da.service.AliPayService; -import com.ai.da.service.OrderInfoService; -import com.alipay.api.AlipayApiException; -import com.alipay.api.AlipayConstants; -import com.alipay.api.internal.util.AlipaySignature; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; -import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; -import java.math.BigDecimal; import java.util.Map; @CrossOrigin @@ -27,22 +20,13 @@ public class AliPayController { @Resource private AliPayService aliPayService; - @Resource - private Environment config; - - @Resource - private OrderInfoService orderInfoService; - @ApiOperation("统一收单下单并支付页面接口的调用") - @PostMapping("/trade/page/pay/{productId}") - public Response tradePagePay(@PathVariable Long productId,@RequestParam String returnUrl){ - - System.out.println(productId + " " + returnUrl); - + @PostMapping("/trade/page/pay/{amount}") + public Response tradePagePay(@PathVariable Integer amount, @RequestParam String returnUrl){ log.info("统一收单下单并支付页面接口的调用"); //支付宝开放平台接受 request 请求对象后 // 会为开发者生成一个html 形式的 form表单,包含自动提交的脚本 - String formStr = aliPayService.tradeCreate(productId, returnUrl); + String formStr = aliPayService.tradeCreate(amount, returnUrl); //我们将form表单字符串返回给前端程序,之后前端将会调用自动提交脚本,进行表单的提交 //此时,表单会自动提交到action属性所指向的支付宝开放平台中,从而为用户展示一个支付页面 return Response.success(formStr); @@ -51,80 +35,7 @@ public class AliPayController { @ApiOperation("支付通知") @PostMapping("/trade/notify") public String tradeNotify(@RequestParam Map params){ - - log.info("支付通知正在执行"); - log.info("通知参数 ===> {}", params); - - String result = "failure"; - - try { - //异步通知验签 - boolean signVerified = AlipaySignature.rsaCheckV1( - params, - config.getProperty("alipay.alipay-public-key"), - AlipayConstants.CHARSET_UTF8, - AlipayConstants.SIGN_TYPE_RSA2); //调用SDK验证签名 - - if(!signVerified){ - //验签失败则记录异常日志,并在response中返回failure. - log.error("支付成功异步通知验签失败!"); - return result; - } - - // 验签成功后 - log.info("支付成功异步通知验签成功!"); - - //按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验, - //1 商户需要验证该通知数据中的 out_trade_no 是否为商户系统中创建的订单号 - String outTradeNo = params.get("out_trade_no"); - OrderInfo order = orderInfoService.getOrderByOrderNo(outTradeNo); - if(order == null){ - log.error("订单不存在"); - return result; - } - - //2 判断 total_amount 是否确实为该订单的实际金额(即商户订单创建时的金额) - String totalAmount = params.get("total_amount"); - int totalAmountInt = new BigDecimal(totalAmount).multiply(new BigDecimal("100")).intValue(); - int totalFeeInt = order.getTotalFee().intValue(); - if(totalAmountInt != totalFeeInt){ - log.error("金额校验失败"); - return result; - } - - //3 校验通知中的 seller_id(或者 seller_email) 是否为 out_trade_no 这笔单据的对应的操作方 - String sellerId = params.get("seller_id"); - String sellerIdProperty = config.getProperty("alipay.seller-id"); - if(!sellerId.equals(sellerIdProperty)){ - log.error("商家pid校验失败"); - return result; - } - - //4 验证 app_id 是否为该商户本身 - String appId = params.get("app_id"); - String appIdProperty = config.getProperty("alipay.app-id"); - if(!appId.equals(appIdProperty)){ - log.error("appid校验失败"); - return result; - } - - //在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS时, - // 支付宝才会认定为买家付款成功。 - String tradeStatus = params.get("trade_status"); - if(!"TRADE_SUCCESS".equals(tradeStatus)){ - log.error("支付未成功"); - return result; - } - - //处理业务 修改订单状态 记录支付日志 - aliPayService.processOrder(params); - - //校验成功后在response中返回success并继续商户自身业务处理,校验失败返回failure - result = "success"; - } catch (AlipayApiException e) { - e.printStackTrace(); - } - return result; + return aliPayService.tradeNotify(params); } /** @@ -135,7 +46,6 @@ public class AliPayController { @ApiOperation("用户取消订单") @PostMapping("/trade/close/{orderNo}") public Response cancel(@PathVariable String orderNo){ - log.info("取消订单"); aliPayService.cancelOrder(orderNo); return Response.success("订单已取消"); @@ -149,16 +59,14 @@ public class AliPayController { @ApiOperation("查询订单:测试订单状态用") @GetMapping("/trade/query/{orderNo}") public Response queryOrder(@PathVariable String orderNo) { - log.info("查询订单"); - String result = aliPayService.queryOrder(orderNo); return Response.success(result); } /** - * 申请退款 + * 不在页面提供申请退款接口 * @param orderNo * @param reason * @return @@ -166,7 +74,6 @@ public class AliPayController { @ApiOperation("申请退款") @PostMapping("/trade/refund/{orderNo}/{reason}") public Response refunds(@PathVariable String orderNo, @PathVariable String reason){ - log.info("申请退款"); aliPayService.refund(orderNo, reason); return Response.success(); @@ -180,10 +87,8 @@ public class AliPayController { */ @ApiOperation("查询退款:测试用") @GetMapping("/trade/fastpay/refund/{orderNo}") - public Response queryRefund(@PathVariable String orderNo) throws Exception { - + public Response queryRefund(@PathVariable String orderNo) { log.info("查询退款"); - String result = aliPayService.queryRefund(orderNo); return Response.success(result); } diff --git a/src/main/java/com/ai/da/controller/ConvenientInquiryController.java b/src/main/java/com/ai/da/controller/ConvenientInquiryController.java new file mode 100644 index 00000000..1cc2552a --- /dev/null +++ b/src/main/java/com/ai/da/controller/ConvenientInquiryController.java @@ -0,0 +1,54 @@ +package com.ai.da.controller; + + +import com.ai.da.common.context.UserContext; +import com.ai.da.common.response.Response; +import com.ai.da.mapper.primary.DesignMapper; +import com.ai.da.mapper.primary.TrialOrderMapper; +import com.ai.da.mapper.primary.entity.TrialOrder; +import com.ai.da.model.dto.UserDesignStatisticDTO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +@Api(tags = "便利查询") +@Slf4j +@RestController +@RequestMapping("/api/inquiry") +public class ConvenientInquiryController { + + @Resource + private TrialOrderMapper trialOrderMapper; + + @Resource + private DesignMapper designMapper; + + @ApiOperation("获取当前所有试用用户") + @GetMapping("/getTrial") + public Response> getTrial(){ + Long accountId = UserContext.getUserHolder().getId(); + if (accountId.equals(31L) || accountId.equals(87L) || accountId.equals(83L)){ + List trialOrders = trialOrderMapper.selectList(null); + return Response.success(trialOrders); + }else { + return Response.fail("Sorry, you don't have permission"); + } + } + + @ApiOperation("获取指定时间区间内所有用户design的使用情况") + @GetMapping("/getDesignStatistic") + public Response> getDesignStatistic(@RequestParam String startTime,@RequestParam String endTime){ + Long accountId = UserContext.getUserHolder().getId(); + if (accountId.equals(31L) || accountId.equals(87L) || accountId.equals(83L)){ + List designStatistic = designMapper.getDesignStatistic(startTime, endTime); + return Response.success(designStatistic); + }else { + return Response.fail("Sorry, you don't have permission"); + } + } + +} diff --git a/src/main/java/com/ai/da/controller/CreditsController.java b/src/main/java/com/ai/da/controller/CreditsController.java new file mode 100644 index 00000000..ab02e57f --- /dev/null +++ b/src/main/java/com/ai/da/controller/CreditsController.java @@ -0,0 +1,42 @@ +package com.ai.da.controller; + +import com.ai.da.common.context.UserContext; +import com.ai.da.common.response.PageBaseResponse; +import com.ai.da.common.response.Response; +import com.ai.da.mapper.primary.entity.CreditsDetail; +import com.ai.da.model.dto.QueryIncomeOrExpenditureDTO; +import com.ai.da.service.CreditsService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; + +@CrossOrigin +@RestController +@RequestMapping("/api/credits") +@Api(tags = "积分") +@Slf4j +public class CreditsController { + + @Resource + private CreditsService creditsService; + + @ApiOperation("获取当前积分") + @GetMapping("/getCredits") + public Response getCredits() { + String credits = creditsService.getCredits(UserContext.getUserHolder().getId()); + return Response.success(credits); + } + + @ApiOperation("获取积分详细") + @PostMapping("/getCreditsDetail") + public Response> getCreditsDetail(@Valid @RequestBody QueryIncomeOrExpenditureDTO queryPageByTimeDTO) { + PageBaseResponse credits = creditsService.queryCreditsDetailsPage(queryPageByTimeDTO); + return Response.success(credits); + } + + +} diff --git a/src/main/java/com/ai/da/controller/OrderInfoController.java b/src/main/java/com/ai/da/controller/OrderInfoController.java index 63e3463c..3a9ce1d5 100644 --- a/src/main/java/com/ai/da/controller/OrderInfoController.java +++ b/src/main/java/com/ai/da/controller/OrderInfoController.java @@ -1,15 +1,17 @@ package com.ai.da.controller; import com.ai.da.common.enums.OrderStatusEnum; +import com.ai.da.common.response.PageBaseResponse; import com.ai.da.common.response.Response; import com.ai.da.mapper.primary.entity.OrderInfo; +import com.ai.da.model.dto.QueryPageByTimeDTO; import com.ai.da.service.OrderInfoService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; -import java.util.List; +import javax.validation.Valid; @CrossOrigin //开放前端的跨域访问 @Api(tags = "商品订单管理") @@ -21,11 +23,11 @@ public class OrderInfoController { private OrderInfoService orderInfoService; @ApiOperation("订单列表") - @GetMapping("/list") - public Response> list(){ - - List list = orderInfoService.listOrderByCreateTimeDesc(); - return Response.success(list); + @PostMapping("/list") + public Response> list(@Valid @RequestBody QueryPageByTimeDTO queryPageByTimeDTO){ + PageBaseResponse orderByAccountId = orderInfoService.getOrderByPage(queryPageByTimeDTO); +// List list = orderInfoService.listOrderByCreateTimeDesc(); + return Response.success(orderByAccountId); } /** @@ -36,15 +38,11 @@ public class OrderInfoController { @ApiOperation("查询本地订单状态") @GetMapping("/query-order-status/{orderNo}") public Response queryOrderStatus(@PathVariable String orderNo){ - String orderStatus = orderInfoService.getOrderStatus(orderNo); if(OrderStatusEnum.SUCCESS.getType().equals(orderStatus)){ return Response.success("支付成功"); //支付成功 } - return Response.success(101,"支付中......"); } - - } diff --git a/src/main/java/com/ai/da/controller/PayPalCheckoutController.java b/src/main/java/com/ai/da/controller/PayPalCheckoutController.java new file mode 100644 index 00000000..2ed119a7 --- /dev/null +++ b/src/main/java/com/ai/da/controller/PayPalCheckoutController.java @@ -0,0 +1,77 @@ +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; +import com.paypal.orders.Order; +import com.paypal.payments.Refund; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashMap; + +@RestController +@Api(tags = "PayPalCheckout接口") +@RequestMapping("/api/paypal") +public class PayPalCheckoutController { + + @Resource + private PayPalCheckoutService payPalCheckoutService; + + @ApiOperation(value = "创建订单") + @PostMapping(value = "/trade/{amount}") + public Response> createOrder(@PathVariable Integer amount, @RequestParam String returnUrl) throws SerializeException { + HashMap approvalUrl = payPalCheckoutService.createOrder(amount,returnUrl); + return Response.success(approvalUrl); + } + + @ApiOperation(value = "ipn异步回调") + @PostMapping(value = "/ipn/back") + public Response callback(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + Boolean result = payPalCheckoutService.doPost(request, response); + if (result){ + return Response.success(200,"success"); + }else { + return Response.fail(500,"failure"); + } + } + + @ApiOperation(value = "查询指定订单") + @PostMapping(value = "/trade/query/{orderNo}") + public Response queryOrder(@PathVariable String orderNo) throws SerializeException { + String s = payPalCheckoutService.queryOrder(orderNo); + return Response.success(s); + } + + /** 不提供退款接口 */ + @ApiOperation("申请退款") + @PostMapping("/trade/refund/{orderNo}/{reason}") + public Response> refund(@PathVariable String orderNo, @PathVariable String reason) throws IOException { + Boolean response = payPalCheckoutService.refundOrder(orderNo,reason); + if (response){ + return Response.success(); + }else { + return Response.fail("Request for refund failed."); + } + + } + + @ApiOperation("执行扣款") + @PostMapping("/trade/capture/{orderNo}") + public Response captureOrder(@PathVariable String orderNo) throws IOException { + Order response = payPalCheckoutService.captureOrder(orderNo); + return Response.success(response); + } + + + +} + diff --git a/src/main/java/com/ai/da/controller/PythonController.java b/src/main/java/com/ai/da/controller/PythonController.java index ea33fe7f..895d311a 100644 --- a/src/main/java/com/ai/da/controller/PythonController.java +++ b/src/main/java/com/ai/da/controller/PythonController.java @@ -7,12 +7,14 @@ import com.ai.da.mapper.primary.entity.Library; import com.ai.da.model.dto.ChatFlushDTO; import com.ai.da.model.dto.ChatRobotLibraryDTO; import com.ai.da.model.dto.ChatSendDTO; +import com.ai.da.model.dto.SuperResolutionDTO; import com.ai.da.model.vo.ChatRobotVO; import com.ai.da.model.vo.PythonLibraryVo; import com.ai.da.model.vo.SysFileVO; import com.ai.da.python.PythonService; import com.ai.da.service.ChatRobotService; import com.ai.da.service.LibraryService; +import com.ai.da.service.SuperResolutionService; import com.ai.da.service.SysFileService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -41,10 +43,12 @@ public class PythonController { private SysFileService sysFileService; @Resource private LibraryService libraryService; - @Resource private ChatRobotService chatRobotService; + @Resource + private SuperResolutionService superResolutionService; + @ApiOperation(value = "python服务保存图片到java服务") @PostMapping("/saveGeneratePicture") public Response upload(@RequestParam("file") MultipartFile file, @@ -109,4 +113,10 @@ public class PythonController { return Response.success(chatRobotService.chatBufferFlush(chatFlushDTO)); } + @ApiOperation(value = "超分辨率") + @PostMapping("/prepareForSR") + public Response> superResolution(@RequestBody List superResolutionDTO) { + return Response.success(superResolutionService.prepareForSR(superResolutionDTO)); + } + } diff --git a/src/main/java/com/ai/da/controller/TaskListController.java b/src/main/java/com/ai/da/controller/TaskListController.java new file mode 100644 index 00000000..85485d50 --- /dev/null +++ b/src/main/java/com/ai/da/controller/TaskListController.java @@ -0,0 +1,40 @@ +package com.ai.da.controller; + +import com.ai.da.common.response.PageBaseResponse; +import com.ai.da.common.response.Response; +import com.ai.da.model.dto.QueryTaskHistoryDTO; +import com.ai.da.model.dto.SuperResolutionDTO; +import com.ai.da.model.dto.TaskDTO; +import com.ai.da.model.vo.TaskVO; +import com.ai.da.service.TaskListService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.List; + +@Api(tags = "任务列表模块") +@Slf4j +@RestController +@RequestMapping("/api/tasks") +public class TaskListController { + + @Resource + private TaskListService taskListService; + + @PostMapping("/getList") + @ApiOperation("获取未执行完的任务") + public Response>> getTaskList(@Valid @RequestBody List taskIdList) { + return Response.success(taskListService.getExecTask(taskIdList)); + } + + @PostMapping("/getAllTask") + @ApiOperation("获取所有任务") + public Response> getAllTask(@Valid @RequestBody QueryTaskHistoryDTO queryTaskHistoryDTO) { + return Response.success(taskListService.getAllTask(queryTaskHistoryDTO)); + } + +} diff --git a/src/main/java/com/ai/da/mapper/primary/CreditsDetailMapper.java b/src/main/java/com/ai/da/mapper/primary/CreditsDetailMapper.java new file mode 100644 index 00000000..1ee33d4e --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/CreditsDetailMapper.java @@ -0,0 +1,7 @@ +package com.ai.da.mapper.primary; + +import com.ai.da.mapper.primary.entity.CreditsDetail; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface CreditsDetailMapper extends BaseMapper { +} diff --git a/src/main/java/com/ai/da/mapper/primary/DesignMapper.java b/src/main/java/com/ai/da/mapper/primary/DesignMapper.java index 5a7e201d..e6b1aa58 100644 --- a/src/main/java/com/ai/da/mapper/primary/DesignMapper.java +++ b/src/main/java/com/ai/da/mapper/primary/DesignMapper.java @@ -1,6 +1,10 @@ package com.ai.da.mapper.primary; import com.ai.da.common.config.mybatis.plus.CommonMapper; +//import com.ai.da.mapper.entity.Design; +import com.ai.da.model.dto.UserDesignStatisticDTO; + +import java.util.List; import com.ai.da.mapper.primary.entity.Design; /** @@ -13,4 +17,6 @@ public interface DesignMapper extends CommonMapper { //返回插入数据后生成的主键 Long insertDesign(Design design); + + List getDesignStatistic(String startTime, String endTime); } diff --git a/src/main/java/com/ai/da/mapper/primary/TaskListMapper.java b/src/main/java/com/ai/da/mapper/primary/TaskListMapper.java new file mode 100644 index 00000000..cbda70e9 --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/TaskListMapper.java @@ -0,0 +1,7 @@ +package com.ai.da.mapper.primary; + +import com.ai.da.mapper.primary.entity.TaskList; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface TaskListMapper extends BaseMapper { +} diff --git a/src/main/java/com/ai/da/mapper/primary/entity/Account.java b/src/main/java/com/ai/da/mapper/primary/entity/Account.java index bca34dec..ad574fe7 100644 --- a/src/main/java/com/ai/da/mapper/primary/entity/Account.java +++ b/src/main/java/com/ai/da/mapper/primary/entity/Account.java @@ -8,6 +8,7 @@ import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; import java.io.Serializable; +import java.math.BigDecimal; import java.util.Date; /** @@ -80,4 +81,9 @@ public class Account implements Serializable { private Integer isBeginner; private String browserIdentifiers; + + /** + * 积分 + */ + private BigDecimal credits; } diff --git a/src/main/java/com/ai/da/mapper/primary/entity/BaseEntity.java b/src/main/java/com/ai/da/mapper/primary/entity/BaseEntity.java index de693f6b..f78fb748 100644 --- a/src/main/java/com/ai/da/mapper/primary/entity/BaseEntity.java +++ b/src/main/java/com/ai/da/mapper/primary/entity/BaseEntity.java @@ -6,6 +6,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import java.io.Serializable; +import java.time.LocalDateTime; import java.util.Date; @Data @@ -23,12 +24,12 @@ public class BaseEntity implements Serializable { /** * 创建时间 */ - private Date createTime; + private LocalDateTime createTime; /** * 更新时间 */ - private Date updateTime; + private LocalDateTime updateTime; /** * 是否已删除 diff --git a/src/main/java/com/ai/da/mapper/primary/entity/CreditsDetail.java b/src/main/java/com/ai/da/mapper/primary/entity/CreditsDetail.java new file mode 100644 index 00000000..1422ba4c --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/entity/CreditsDetail.java @@ -0,0 +1,21 @@ +package com.ai.da.mapper.primary.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("t_credits_detail") +public class CreditsDetail extends BaseEntity { + /** 用户id */ + private Long accountId; + /** 积分变更事件 */ + private String changeEvent; + /** 变更积分 ( + 表示加,- 表示减) */ + private String changedCredits; + /** 当前积分 */ + private BigDecimal credits; +} diff --git a/src/main/java/com/ai/da/mapper/primary/entity/OrderInfo.java b/src/main/java/com/ai/da/mapper/primary/entity/OrderInfo.java index a4668176..c3b0be10 100644 --- a/src/main/java/com/ai/da/mapper/primary/entity/OrderInfo.java +++ b/src/main/java/com/ai/da/mapper/primary/entity/OrderInfo.java @@ -11,11 +11,11 @@ public class OrderInfo extends BaseEntity{ private String orderNo;//商户订单编号 - private Long userId;//用户id + private Long accountId;//用户id private Long productId;//支付产品id - private Integer totalFee;//订单金额(分) + private Integer totalFee;//订单金额(元) private String codeUrl;//订单二维码连接 diff --git a/src/main/java/com/ai/da/mapper/primary/entity/RefundInfo.java b/src/main/java/com/ai/da/mapper/primary/entity/RefundInfo.java index ee07efc8..19719a98 100644 --- a/src/main/java/com/ai/da/mapper/primary/entity/RefundInfo.java +++ b/src/main/java/com/ai/da/mapper/primary/entity/RefundInfo.java @@ -11,7 +11,7 @@ public class RefundInfo extends BaseEntity{ private String refundNo;//退款单编号 - private String refundId;//支付系统退款单号 + private String refundId;//支付系统退款单号(微信) private Integer totalFee;//原订单金额(分) diff --git a/src/main/java/com/ai/da/mapper/primary/entity/TaskList.java b/src/main/java/com/ai/da/mapper/primary/entity/TaskList.java new file mode 100644 index 00000000..81797448 --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/entity/TaskList.java @@ -0,0 +1,27 @@ +package com.ai.da.mapper.primary.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("t_task_list") +public class TaskList extends BaseEntity{ + + private Long accountId; + + private String taskType; + + private String inputUrl; + + private Integer scale; + + /* 可选状态 : 成功:success 失败:fail */ + private String status; + + private String outputUrl; + + private String taskId; + +} diff --git a/src/main/java/com/ai/da/model/dto/QueryIncomeOrExpenditureDTO.java b/src/main/java/com/ai/da/model/dto/QueryIncomeOrExpenditureDTO.java new file mode 100644 index 00000000..748d6108 --- /dev/null +++ b/src/main/java/com/ai/da/model/dto/QueryIncomeOrExpenditureDTO.java @@ -0,0 +1,13 @@ +package com.ai.da.model.dto; + +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +@ApiModel("查积分的收支详情") +public class QueryIncomeOrExpenditureDTO extends QueryPageByTimeDTO{ + + private Boolean isIncome; +} diff --git a/src/main/java/com/ai/da/model/dto/QueryPageByTimeDTO.java b/src/main/java/com/ai/da/model/dto/QueryPageByTimeDTO.java new file mode 100644 index 00000000..c0c37a98 --- /dev/null +++ b/src/main/java/com/ai/da/model/dto/QueryPageByTimeDTO.java @@ -0,0 +1,19 @@ +package com.ai.da.model.dto; + +import com.ai.da.model.vo.PageQueryBaseVo; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +@ApiModel("分页查询,限制时间区间") +public class QueryPageByTimeDTO extends PageQueryBaseVo { + + @ApiModelProperty("开始时间 yyyy-mm-dd hh:mm:ss 可以不要时分秒") + private String startTime; + + @ApiModelProperty("结束时间 yyyy-mm-dd hh:mm:ss 可以不要时分秒") + private String endTime; +} diff --git a/src/main/java/com/ai/da/model/dto/QueryTaskHistoryDTO.java b/src/main/java/com/ai/da/model/dto/QueryTaskHistoryDTO.java new file mode 100644 index 00000000..db8dbc69 --- /dev/null +++ b/src/main/java/com/ai/da/model/dto/QueryTaskHistoryDTO.java @@ -0,0 +1,18 @@ +package com.ai.da.model.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotEmpty; + +@EqualsAndHashCode(callSuper = true) +@Data +@ApiModel("按分类分页查询历史任务") +public class QueryTaskHistoryDTO extends QueryPageByTimeDTO { + + @NotEmpty(message = "type cannot be empty") + @ApiModelProperty("可选类型 : SR") + private String type; +} diff --git a/src/main/java/com/ai/da/model/dto/SuperResolutionDTO.java b/src/main/java/com/ai/da/model/dto/SuperResolutionDTO.java new file mode 100644 index 00000000..36b15ce6 --- /dev/null +++ b/src/main/java/com/ai/da/model/dto/SuperResolutionDTO.java @@ -0,0 +1,25 @@ +package com.ai.da.model.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SuperResolutionDTO { + + @NotBlank(message = "You have to select at least one image") + @ApiModelProperty("图片") + private String images; + + @NotBlank(message = "You must choose the magnification") + @ApiModelProperty("放大倍数") + private Integer scale; + + @ApiModelProperty("唯一id,用于保持消息唯一性") + private String uniqueId; +} diff --git a/src/main/java/com/ai/da/model/dto/TaskDTO.java b/src/main/java/com/ai/da/model/dto/TaskDTO.java new file mode 100644 index 00000000..83b3c7fa --- /dev/null +++ b/src/main/java/com/ai/da/model/dto/TaskDTO.java @@ -0,0 +1,46 @@ +package com.ai.da.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TaskDTO { + + private String taskId; + + // 可选type : SR GENERATE + private String type; + + // 输入的图片名 + private String imageName; + + private T inputParam; + + private String outputImage; + + // 任务状态,暂定状态:排队中、执行中、成功/失败 + private String status; + + // 当前任务的创建时间 + private String createDate; + + public TaskDTO(String taskId, String type, String imageName, T inputParam, String status, String createDate) { + this.taskId = taskId; + this.type = type; + this.imageName = imageName; + this.inputParam = inputParam; + this.status = status; + this.createDate = createDate; + } + + public TaskDTO(String taskId, String type, T inputParam, String status, String createDate) { + this.taskId = taskId; + this.type = type; + this.inputParam = inputParam; + this.status = status; + this.createDate = createDate; + } +} diff --git a/src/main/java/com/ai/da/model/dto/UserDesignStatisticDTO.java b/src/main/java/com/ai/da/model/dto/UserDesignStatisticDTO.java new file mode 100644 index 00000000..25bd7038 --- /dev/null +++ b/src/main/java/com/ai/da/model/dto/UserDesignStatisticDTO.java @@ -0,0 +1,17 @@ +package com.ai.da.model.dto; + +import lombok.Data; + +@Data +public class UserDesignStatisticDTO { + + private Long accountId; + + private Long useDesignTimes; + + private String userEmail; + + private String userName; + + private String isTrial; +} diff --git a/src/main/java/com/ai/da/model/dto/WebhookVerifyDTO.java b/src/main/java/com/ai/da/model/dto/WebhookVerifyDTO.java new file mode 100644 index 00000000..4bcbc352 --- /dev/null +++ b/src/main/java/com/ai/da/model/dto/WebhookVerifyDTO.java @@ -0,0 +1,35 @@ +package com.ai.da.model.dto; + +import com.alibaba.fastjson.JSONObject; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class WebhookVerifyDTO implements Serializable { + + private String transmission_id; + private String transmission_time; + private String cert_url; + private String auth_algo; + private String transmission_sig; + private String webhook_id; + private Object webhook_event; + +} + + + + + + + + + diff --git a/src/main/java/com/ai/da/model/vo/TaskVO.java b/src/main/java/com/ai/da/model/vo/TaskVO.java new file mode 100644 index 00000000..1c0a3f63 --- /dev/null +++ b/src/main/java/com/ai/da/model/vo/TaskVO.java @@ -0,0 +1,22 @@ +package com.ai.da.model.vo; + +import lombok.Data; + +@Data +public class TaskVO { + + // 图片名 + private String imageName; + + private String inputImage; + + private String outputImage; + + private String otherInput; + + private String status; + + private String taskId; + + private String createDate; +} diff --git a/src/main/java/com/ai/da/python/PythonService.java b/src/main/java/com/ai/da/python/PythonService.java index d1bbb587..2907a19d 100644 --- a/src/main/java/com/ai/da/python/PythonService.java +++ b/src/main/java/com/ai/da/python/PythonService.java @@ -65,6 +65,8 @@ public class PythonService { private String accessPythonIp; @Value("${access.python.port:''}") private String accessPythonPort; + @Value("${access.python.sr}") + private String srPythonPort; @Resource private PythonTAllInfoService pythonTAllInfoService; @@ -290,7 +292,7 @@ public class PythonService { List pinData = getPinData(elementVO); if (CollectionUtil.isNotEmpty(pinData)) { return CurrentDesignPictureTypeEnum.PIN; - }else { + } else { if (sysSketchNum == 0 && noPinSketchNum == 0) { sysSketchNum = systemScale.multiply(BigDecimal.valueOf(8 - pinSketchNum)).setScale(0, BigDecimal.ROUND_HALF_UP).intValue(); noPinSketchNum = 8 - pinSketchNum - sysSketchNum; @@ -605,6 +607,7 @@ public class PythonService { @Resource private AttributeRetrievalMapper attributeRetrievalMapper; + private List getSystemSketchPool(JSONObject attributeRecognition, String styleCategory, String modelSex, int poolNum) { /** * female trousers->female_pants @@ -847,6 +850,7 @@ public class PythonService { private CollocationMapper collocationMapper; @Resource private DressingMapper dressingMapper; + private List getOtherSketchCategoryList(String modelSex, DesignPythonItem designPythonItem) { List collocationList = collocationMapper.getCollocationListBySketch(modelSex, designPythonItem.getType()); if (CollectionUtil.isNotEmpty(collocationList)) { @@ -2003,10 +2007,10 @@ public class PythonService { if (elementVO.getSingleOverall().equals(SingleOverallEnum.SINGLE.getRealName())) { if (!CollectionUtils.isEmpty(pinData)) { return pinData.stream().filter(o -> o.getLevel2Type().equals(elementVO.getSwitchCategory())).collect(Collectors.toList()); - }else { + } else { return pinData; } - }else { + } else { return pinData; } } @@ -2016,10 +2020,10 @@ public class PythonService { if (elementVO.getSingleOverall().equals(SingleOverallEnum.SINGLE.getRealName())) { if (!CollectionUtils.isEmpty(pinData)) { return pinData.stream().filter(o -> o.getLevel2Type().equals(elementVO.getSwitchCategory())).collect(Collectors.toList()); - }else { + } else { return pinData; } - }else { + } else { return pinData; } } @@ -2042,8 +2046,8 @@ public class PythonService { return sketchBoardPins; } - public final static List FEMALE_CATEGORY = Arrays.asList("Dress","Blouse","Skirt","Trousers","Outwear"); - public final static List MALE_CATEGORY = Arrays.asList("Tops","Bottoms","Outwear"); + public final static List FEMALE_CATEGORY = Arrays.asList("Dress", "Blouse", "Skirt", "Trousers", "Outwear"); + public final static List MALE_CATEGORY = Arrays.asList("Tops", "Bottoms", "Outwear"); private List getPinData(List sketchBoardElements, List hasUseMd5List, String modelSex) { if (CollectionUtils.isEmpty(sketchBoardElements)) { @@ -2070,7 +2074,7 @@ public class PythonService { return null; } return noPinData.stream().filter(o -> o.getLevel2Type().equals(elementVO.getSwitchCategory())).collect(Collectors.toList()); - }else { + } else { return getNoPinData(elementVO.getSketchBoardElements(), elementVO.getModelSex()); } } @@ -2929,6 +2933,7 @@ public class PythonService { Boolean result = JSON.parseObject(JSON.toJSONString(response)).getBoolean("successful"); if (result && jsonObject.get("code").equals(200)) { + log.info("Generate##responseObject###{}", jsonObject); return setGenerateImageList(jsonObject.getJSONObject("data")); } log.info("generateSketchOrPrintPrint失败###{}", jsonObject); @@ -3074,7 +3079,7 @@ public class PythonService { // .addHeader("Authorization", "Basic YWlkbGFiOjEyMw==") // .addHeader("Content-Type", "application/json") .build(); - Response response; + Response response = null; try { log.info("cancelGenerateTask请求入参content###{}", taskId); response = client.newCall(request).execute(); @@ -3082,8 +3087,10 @@ public class PythonService { log.error("PythonService##cancelGenerateTask异常###{}", ExceptionUtil.getThrowableList(ioException)); return null; } + int responseCode = response.code(); + response.close(); - if (response.code() != HttpURLConnection.HTTP_OK) { + if (responseCode != HttpURLConnection.HTTP_OK) { log.info("generate-python 取消请求失败"); return Boolean.FALSE; } @@ -3091,4 +3098,45 @@ public class PythonService { return Boolean.TRUE; } + public Boolean superResolution(SuperResolutionDTO superResolutionDTO) throws BusinessException { + OkHttpClient client = new OkHttpClient().newBuilder() + .connectTimeout(30, TimeUnit.SECONDS) + .pingInterval(5, TimeUnit.SECONDS)//websocket轮训间隔(单位:秒) + .readTimeout(60, TimeUnit.SECONDS)//读取超时(单位:秒) + .writeTimeout(60, TimeUnit.SECONDS)//写入超时(单位:秒) + .build(); + MediaType mediaType = MediaType.parse("application/json"); + + HashMap content = new HashMap<>(); + content.put("sr_image_url", superResolutionDTO.getImages()); + content.put("sr_xn", superResolutionDTO.getScale().toString()); + content.put("sr_tasks_id", superResolutionDTO.getUniqueId()); + + String jsonString = JSON.toJSONString(content, SerializerFeature.WriteNullStringAsEmpty); + RequestBody body = RequestBody.create(mediaType, jsonString); + Request request = new Request.Builder() + .url(srPythonPort + "/api/super_resolution") + .method("POST", body) + .addHeader("Content-Type", "application/json") + .build(); + Response response = null; + try { + log.info("superResolution请求入参content###{}", jsonString); + response = client.newCall(request).execute(); + } catch (IOException ioException) { + log.error("PythonService##superResolution异常###{}", ExceptionUtil.getThrowableList(ioException)); + return null; + } + int responseCode = response.code(); + response.close(); + + if (responseCode != HttpURLConnection.HTTP_OK) { + // 基本不会有除200以外的code + log.info("superResolution 请求超分操作失败。 Response code " + response.code()); + throw new BusinessException("superResolution 请求超分操作失败。 Response code " + response.code()); + } + log.info("superResolution 请求超分操作成功"); + return Boolean.TRUE; + } + } diff --git a/src/main/java/com/ai/da/service/AccountService.java b/src/main/java/com/ai/da/service/AccountService.java index c00d2a4c..5b00ba5b 100644 --- a/src/main/java/com/ai/da/service/AccountService.java +++ b/src/main/java/com/ai/da/service/AccountService.java @@ -128,4 +128,6 @@ public interface AccountService extends IService { void upgradeNotification(); void moveLibraryDate(); + + void updateCredits(Long accountId, String value); } diff --git a/src/main/java/com/ai/da/service/AliPayService.java b/src/main/java/com/ai/da/service/AliPayService.java index 796126a2..66dd334d 100644 --- a/src/main/java/com/ai/da/service/AliPayService.java +++ b/src/main/java/com/ai/da/service/AliPayService.java @@ -3,7 +3,9 @@ package com.ai.da.service; import java.util.Map; public interface AliPayService { - String tradeCreate(Long productId,String returnUrl); + String tradeCreate(Integer amount,String returnUrl); + + String tradeNotify(Map params); void processOrder(Map params); diff --git a/src/main/java/com/ai/da/service/CallBackService.java b/src/main/java/com/ai/da/service/CallBackService.java new file mode 100644 index 00000000..39efb1eb --- /dev/null +++ b/src/main/java/com/ai/da/service/CallBackService.java @@ -0,0 +1,12 @@ +package com.ai.da.service; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public interface CallBackService { + + Boolean doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException; +} diff --git a/src/main/java/com/ai/da/service/CreditsService.java b/src/main/java/com/ai/da/service/CreditsService.java new file mode 100644 index 00000000..a2947b5b --- /dev/null +++ b/src/main/java/com/ai/da/service/CreditsService.java @@ -0,0 +1,29 @@ +package com.ai.da.service; + + +import com.ai.da.common.enums.CreditsEventsEnum; +import com.ai.da.common.response.PageBaseResponse; +import com.ai.da.mapper.primary.entity.CreditsDetail; +import com.ai.da.model.dto.QueryIncomeOrExpenditureDTO; +import com.baomidou.mybatisplus.extension.service.IService; + +public interface CreditsService extends IService { + + void initCredits(); + + Boolean buyCredits(Long accountId, Integer quantity); + + void creditsIncrease(Long accountId, String event); + + void creditsDecrease(Long accountId, String event); + + String getCredits(Long accountId); + + void creditsRefund(Long accountId, Integer quantity); + + void insertToCreditsDetail(Long accountId, String changeEvent, String credits, String changeType); + + PageBaseResponse queryCreditsDetailsPage(QueryIncomeOrExpenditureDTO queryPageByTimeDTO); + + Boolean checkCredits(Long accountId, CreditsEventsEnum event, Integer num); +} diff --git a/src/main/java/com/ai/da/service/OrderInfoService.java b/src/main/java/com/ai/da/service/OrderInfoService.java index eaac6df3..35da4881 100644 --- a/src/main/java/com/ai/da/service/OrderInfoService.java +++ b/src/main/java/com/ai/da/service/OrderInfoService.java @@ -2,14 +2,16 @@ package com.ai.da.service; import com.ai.da.common.enums.OrderStatusEnum; +import com.ai.da.common.response.PageBaseResponse; import com.ai.da.mapper.primary.entity.OrderInfo; +import com.ai.da.model.dto.QueryPageByTimeDTO; import com.baomidou.mybatisplus.extension.service.IService; import java.util.List; public interface OrderInfoService extends IService { - OrderInfo createOrderByProductId(Long productId, String paymentType); + OrderInfo createOrderByProductId(Integer productId, String paymentType); void saveCodeUrl(String orderNo, String codeUrl); @@ -22,4 +24,8 @@ public interface OrderInfoService extends IService { List getNoPayOrderByDuration(int minutes, String paymentType); OrderInfo getOrderByOrderNo(String orderNo); + + PageBaseResponse getOrderByPage(QueryPageByTimeDTO queryPageByTimeDTO); + + void updateOrderNoById(Long id, String orderNo); } diff --git a/src/main/java/com/ai/da/service/PayPalCheckoutService.java b/src/main/java/com/ai/da/service/PayPalCheckoutService.java new file mode 100644 index 00000000..a3f0fcdb --- /dev/null +++ b/src/main/java/com/ai/da/service/PayPalCheckoutService.java @@ -0,0 +1,32 @@ +package com.ai.da.service; + +import com.paypal.http.exceptions.SerializeException; +import com.paypal.orders.Order; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public interface PayPalCheckoutService { + + HashMap createOrder(Integer amount,String returnUrl) throws SerializeException; + +// String callback(@SuppressWarnings("rawtypes") Map map); + + Boolean doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException; + + String queryOrder(String orderNo) throws SerializeException; + + Order captureOrder(String orderId) throws IOException; + + Boolean refundOrder(String orderId, String reason) throws IOException; + + String getOAuth(); + + void processOrder(String orderId); +} + diff --git a/src/main/java/com/ai/da/service/PaymentInfoService.java b/src/main/java/com/ai/da/service/PaymentInfoService.java index aa1470e4..4a350dff 100644 --- a/src/main/java/com/ai/da/service/PaymentInfoService.java +++ b/src/main/java/com/ai/da/service/PaymentInfoService.java @@ -1,5 +1,7 @@ package com.ai.da.service; +import com.paypal.orders.Order; + import java.util.Map; public interface PaymentInfoService { @@ -7,4 +9,6 @@ public interface PaymentInfoService { void createPaymentInfo(String plainText); void createPaymentInfoForAliPay(Map params); + + void createPaymentInfoForPayPal(Order order); } diff --git a/src/main/java/com/ai/da/service/RabbitMQService.java b/src/main/java/com/ai/da/service/RabbitMQService.java index b03a578d..7797c905 100644 --- a/src/main/java/com/ai/da/service/RabbitMQService.java +++ b/src/main/java/com/ai/da/service/RabbitMQService.java @@ -5,7 +5,9 @@ import org.springframework.stereotype.Service; @Service public interface RabbitMQService { - void publishMessage(String message); + void publishMessageToGenerate(String message); + + void publishMessageToSR(String message); Integer getMessageCount(String queueUrl); } diff --git a/src/main/java/com/ai/da/service/RefundInfoService.java b/src/main/java/com/ai/da/service/RefundInfoService.java index 3690c794..5cfeca4c 100644 --- a/src/main/java/com/ai/da/service/RefundInfoService.java +++ b/src/main/java/com/ai/da/service/RefundInfoService.java @@ -17,4 +17,6 @@ public interface RefundInfoService extends IService { RefundInfo createRefundByOrderNoForAliPay(String orderNo, String reason); void updateRefundForAliPay(String refundNo, String content, String refundStatus); + + void updateRefundForPayPal(Long id, String refundId, String content, String refundStatus); } diff --git a/src/main/java/com/ai/da/service/SuperResolutionService.java b/src/main/java/com/ai/da/service/SuperResolutionService.java new file mode 100644 index 00000000..8f989e15 --- /dev/null +++ b/src/main/java/com/ai/da/service/SuperResolutionService.java @@ -0,0 +1,22 @@ +package com.ai.da.service; + +import com.ai.da.common.response.PageBaseResponse; +import com.ai.da.mapper.primary.entity.TaskList; +import com.ai.da.model.dto.QueryTaskHistoryDTO; +import com.ai.da.model.dto.SuperResolutionDTO; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +public interface SuperResolutionService extends IService { + + List prepareForSR(List superResolutionDTO); + + void SR(SuperResolutionDTO superResolutionDTO) throws Exception; + + void setSRResult(String taskId, String output, String status); + + void updateSROutput(String taskId, String status, String output); + + PageBaseResponse getTaskHistoryPage(QueryTaskHistoryDTO queryTaskHistoryDTO); +} diff --git a/src/main/java/com/ai/da/service/TaskListService.java b/src/main/java/com/ai/da/service/TaskListService.java new file mode 100644 index 00000000..9f43deb6 --- /dev/null +++ b/src/main/java/com/ai/da/service/TaskListService.java @@ -0,0 +1,22 @@ +package com.ai.da.service; + +import com.ai.da.common.response.PageBaseResponse; +import com.ai.da.mapper.primary.entity.TaskList; +import com.ai.da.model.dto.QueryTaskHistoryDTO; +import com.ai.da.model.dto.SuperResolutionDTO; +import com.ai.da.model.dto.TaskDTO; +import com.ai.da.model.vo.TaskVO; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +public interface TaskListService extends IService { + + List> getExecTask(List taskIdList); + + void addToTaskListRedis(TaskDTO taskDTO); + + void updateTaskStatusOrOutputRedis(String taskId, String status, String output); + + PageBaseResponse getAllTask(QueryTaskHistoryDTO queryTaskHistoryDTO); +} diff --git a/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java b/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java index d673b327..b6e75c34 100644 --- a/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java @@ -36,6 +36,7 @@ import org.springframework.util.Assert; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; +import java.math.BigDecimal; import java.time.Instant; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; @@ -488,6 +489,8 @@ public class AccountServiceImpl extends ServiceImpl impl trialOrder.setStatus(0); trialOrderMapper.insert(trialOrder); SendEmailUtil.sendCustomEmail("1023316923@qq.com", null, trialOrder,1); + SendEmailUtil.sendCustomEmail("calvinwong@aidlab.hk", null, trialOrder,1); + SendEmailUtil.sendCustomEmail("kaicpang.pang@connect.polyu.hk", null, trialOrder,1); // 判断当前的试用订单是否自动批准 if (AutoApproved.getStatus()) { // 改变试用订单状态,新增试用用户 @@ -500,7 +503,7 @@ public class AccountServiceImpl extends ServiceImpl impl account.setIsTrial(1); account.setIsBeginner(1); account.setValidStartTime(System.currentTimeMillis()); - account.setValidEndTime(Instant.now().plus(3, ChronoUnit.DAYS).toEpochMilli()); + account.setValidEndTime(Instant.now().plus(5, ChronoUnit.DAYS).toEpochMilli()); accountMapper.updateById(account); }else { account.setUserName(trialOrder.getUserName()); @@ -508,7 +511,7 @@ public class AccountServiceImpl extends ServiceImpl impl account.setUserEmail(trialOrder.getEmail()); account.setLanguage(Language.ENGLISH.name()); account.setValidStartTime(System.currentTimeMillis()); - account.setValidEndTime(Instant.now().plus(3, ChronoUnit.DAYS).toEpochMilli()); + account.setValidEndTime(Instant.now().plus(5, ChronoUnit.DAYS).toEpochMilli()); account.setCreateDate(new Date()); account.setIsTrial(1); account.setIsBeginner(1); @@ -516,6 +519,8 @@ public class AccountServiceImpl extends ServiceImpl impl } // 发送邮件提醒用户试用用户已创建 SendEmailUtil.sendCustomEmail("1023316923@qq.com", null, trialOrder,2); + SendEmailUtil.sendCustomEmail("calvinwong@aidlab.hk", null, trialOrder,2); + SendEmailUtil.sendCustomEmail("kaicpang.pang@connect.polyu.hk", null, trialOrder,2); SendEmailUtil.sendCustomEmail(account.getUserEmail(), null, trialOrder, 3); } return Boolean.TRUE; @@ -548,7 +553,7 @@ public class AccountServiceImpl extends ServiceImpl impl account.setIsTrial(1); account.setIsBeginner(1); account.setValidStartTime(System.currentTimeMillis()); - account.setValidEndTime(Instant.now().plus(3, ChronoUnit.DAYS).toEpochMilli()); + account.setValidEndTime(Instant.now().plus(5, ChronoUnit.DAYS).toEpochMilli()); accountMapper.updateById(account); }else { account.setUserName(trialOrder.getUserName()); @@ -556,7 +561,7 @@ public class AccountServiceImpl extends ServiceImpl impl account.setUserEmail(trialOrder.getEmail()); account.setLanguage(Language.ENGLISH.name()); account.setValidStartTime(System.currentTimeMillis()); - account.setValidEndTime(Instant.now().plus(3, ChronoUnit.DAYS).toEpochMilli()); + account.setValidEndTime(Instant.now().plus(5, ChronoUnit.DAYS).toEpochMilli()); account.setCreateDate(new Date()); account.setIsTrial(1); account.setIsBeginner(1); @@ -564,6 +569,8 @@ public class AccountServiceImpl extends ServiceImpl impl } // 发送邮件提醒用户试用用户已创建 SendEmailUtil.sendCustomEmail("1023316923@qq.com", null, trialOrder,2); + SendEmailUtil.sendCustomEmail("calvinwong@aidlab.hk", null, trialOrder,2); + SendEmailUtil.sendCustomEmail("kaicpang.pang@connect.polyu.hk", null, trialOrder,2); SendEmailUtil.sendCustomEmail(account.getUserEmail(), null, trialOrder, 3); } return Boolean.TRUE; @@ -885,4 +892,11 @@ public class AccountServiceImpl extends ServiceImpl impl // 未迁移过的进行迁移,注意模特数据迁移打点信息以及转换模特格式 } + + public void updateCredits(Long accountId, String value){ + Account account = new Account(); + account.setId(accountId); + account.setCredits(new BigDecimal(value)); + accountMapper.updateById(account); + } } diff --git a/src/main/java/com/ai/da/service/impl/AliPayServiceImpl.java b/src/main/java/com/ai/da/service/impl/AliPayServiceImpl.java index 38d67eac..e0f5c107 100644 --- a/src/main/java/com/ai/da/service/impl/AliPayServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/AliPayServiceImpl.java @@ -1,17 +1,18 @@ package com.ai.da.service.impl; +import com.ai.da.common.config.exception.BusinessException; import com.ai.da.common.enums.AliPayTradeStateEnum; +import com.ai.da.common.enums.CreditsEventsEnum; import com.ai.da.common.enums.OrderStatusEnum; import com.ai.da.common.enums.PayTypeEnum; import com.ai.da.mapper.primary.entity.OrderInfo; import com.ai.da.mapper.primary.entity.RefundInfo; -import com.ai.da.service.AliPayService; -import com.ai.da.service.OrderInfoService; -import com.ai.da.service.PaymentInfoService; -import com.ai.da.service.RefundInfoService; +import com.ai.da.service.*; import com.alibaba.fastjson.JSONObject; import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayClient; +import com.alipay.api.AlipayConstants; +import com.alipay.api.internal.util.AlipaySignature; import com.alipay.api.request.*; import com.alipay.api.response.*; import com.google.gson.Gson; @@ -46,16 +47,19 @@ public class AliPayServiceImpl implements AliPayService { @Resource private RefundInfoService refundsInfoService; + @Resource + private CreditsService creditsService; + private final ReentrantLock lock = new ReentrantLock(); @Transactional(rollbackFor = Exception.class) @Override - public String tradeCreate(Long productId, String returnUrl) { + public String tradeCreate(Integer amount, String returnUrl) { try { //生成订单 log.info("生成订单"); - OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId, PayTypeEnum.ALIPAY.getType()); + OrderInfo orderInfo = orderInfoService.createOrderByProductId(amount, PayTypeEnum.ALIPAY.getType()); //调用支付宝接口 AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); @@ -69,9 +73,10 @@ public class AliPayServiceImpl implements AliPayService { //组装当前业务方法的请求参数 JSONObject bizContent = new JSONObject(); bizContent.put("out_trade_no", orderInfo.getOrderNo()); - BigDecimal total = new BigDecimal(orderInfo.getTotalFee().toString()).divide(new BigDecimal("100")); + BigDecimal total = new BigDecimal(orderInfo.getTotalFee().toString()); bizContent.put("total_amount", total); - bizContent.put("subject", orderInfo.getTitle()); +// bizContent.put("subject", orderInfo.getTitle()); + bizContent.put("subject", "积分购买"); bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY"); request.setBizContent(bizContent.toString()); @@ -84,14 +89,93 @@ public class AliPayServiceImpl implements AliPayService { return response.getBody(); } else { log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg()); - throw new RuntimeException("创建支付交易失败"); + throw new BusinessException("Order creation failed"); } } catch (AlipayApiException e) { e.printStackTrace(); - throw new RuntimeException("创建支付交易失败"); + throw new BusinessException("Order creation failed"); } } + @Override + public String tradeNotify(Map params) { + log.info("支付通知正在执行"); + log.info("通知参数 ===> {}", params); + + String result = "failure"; + + try { + //异步通知验签 + boolean signVerified = AlipaySignature.rsaCheckV1( + params, + config.getProperty("alipay.alipay-public-key"), + AlipayConstants.CHARSET_UTF8, + AlipayConstants.SIGN_TYPE_RSA2); //调用SDK验证签名 + + if(!signVerified){ + //验签失败则记录异常日志,并在response中返回failure. + log.error("支付成功异步通知验签失败!"); + return result; + } + + // 验签成功后 + log.info("支付成功异步通知验签成功!"); + + //按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验, + //1 商户需要验证该通知数据中的 out_trade_no 是否为商户系统中创建的订单号 + String outTradeNo = params.get("out_trade_no"); + OrderInfo order = orderInfoService.getOrderByOrderNo(outTradeNo); + if(order == null){ + log.error("订单不存在"); + return result; + } + + //2 判断 total_amount 是否确实为该订单的实际金额(即商户订单创建时的金额) + String totalAmount = params.get("total_amount"); + int totalAmountInt = new BigDecimal(totalAmount).intValue(); + int totalFeeInt = order.getTotalFee().intValue(); + if(totalAmountInt != totalFeeInt){ + log.error("金额校验失败"); + return result; + } + + //3 校验通知中的 seller_id(或者 seller_email) 是否为 out_trade_no 这笔单据的对应的操作方 + String sellerId = params.get("seller_id"); + String sellerIdProperty = config.getProperty("alipay.seller-id"); + if(!sellerId.equals(sellerIdProperty)){ + log.error("商家pid校验失败"); + return result; + } + + //4 验证 app_id 是否为该商户本身 + String appId = params.get("app_id"); + String appIdProperty = config.getProperty("alipay.app-id"); + if(!appId.equals(appIdProperty)){ + log.error("appid校验失败"); + return result; + } + + //在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS时, + // 支付宝才会认定为买家付款成功。 + String tradeStatus = params.get("trade_status"); + if(!"TRADE_SUCCESS".equals(tradeStatus)){ + log.error("支付未成功"); + return result; + } + + //处理业务 修改订单状态 记录支付日志 + processOrder(params); + + //校验成功后在response中返回success并继续商户自身业务处理,校验失败返回failure + result = "success"; + } catch (AlipayApiException e) { + e.printStackTrace(); + } + + return result; + } + + /** * 处理订单 * @param params @@ -104,6 +188,7 @@ public class AliPayServiceImpl implements AliPayService { //获取订单号 String orderNo = params.get("out_trade_no"); + String totalAmount = params.get("total_amount"); /*在对业务数据进行状态检查和处理之前, 要采用数据锁进行并发控制, @@ -112,26 +197,31 @@ public class AliPayServiceImpl implements AliPayService { // 成功获取则立即返回true,获取失败则立即返回false。不必一直等待锁的释放 if(lock.tryLock()) { try { - //处理重复通知 //接口调用的幂等性:无论接口被调用多少次,以下业务执行一次 - String orderStatus = orderInfoService.getOrderStatus(orderNo); - if (!OrderStatusEnum.NOT_PAY.getType().equals(orderStatus)) { +// String orderStatus = orderInfoService.getOrderStatus(orderNo); + OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderNo); + String orderStatus = orderByOrderNo.getOrderStatus(); + // 当订单状态处于未支付或超时已关闭时,更新订单状态 + if (!OrderStatusEnum.NOT_PAY.getType().equals(orderStatus) || !OrderStatusEnum.TIMEOUT_CLOSED.getType().equals(orderStatus)) { return; } - //更新订单状态 orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.SUCCESS); - //记录支付日志 paymentInfoService.createPaymentInfoForAliPay(params); - + // 添加积分变更记录 + creditsService.insertToCreditsDetail(orderByOrderNo.getAccountId(), + CreditsEventsEnum.BUY_CREDITS.getName() + "--Alipay", + CreditsEventsEnum.BUY_CREDITS.getValue(), + "positive"); + // 更新积分 + creditsService.buyCredits(orderByOrderNo.getAccountId(),Integer.parseInt(totalAmount) / Integer.parseInt(CreditsEventsEnum.PRICE.getValue())); } finally { //要主动释放锁 lock.unlock(); } } - } /** @@ -176,7 +266,7 @@ public class AliPayServiceImpl implements AliPayService { } catch (AlipayApiException e) { e.printStackTrace(); - throw new RuntimeException("查单接口的调用失败"); + throw new BusinessException("查单接口的调用失败"); } } @@ -198,7 +288,7 @@ public class AliPayServiceImpl implements AliPayService { if(result == null){ log.warn("核实订单未创建 ===> {}", orderNo); //更新本地订单状态 - orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.CLOSED); + orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.TIMEOUT_CLOSED); } //解析查单响应结果 @@ -207,24 +297,28 @@ public class AliPayServiceImpl implements AliPayService { LinkedTreeMap alipayTradeQueryResponse = resultMap.get("alipay_trade_query_response"); String tradeStatus = (String)alipayTradeQueryResponse.get("trade_status"); + OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderNo); if(AliPayTradeStateEnum.NOTPAY.getType().equals(tradeStatus)){ log.warn("核实订单未支付 ===> {}", orderNo); - //如果订单未支付,则调用关单接口关闭订单 this.closeOrder(orderNo); - // 并更新商户端订单状态 - orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.CLOSED); + orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.TIMEOUT_CLOSED); } if(AliPayTradeStateEnum.SUCCESS.getType().equals(tradeStatus)){ log.warn("核实订单已支付 ===> {}", orderNo); - //如果订单已支付,则更新商户端订单状态 orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.SUCCESS); - //并记录支付日志 paymentInfoService.createPaymentInfoForAliPay(alipayTradeQueryResponse); + // 添加积分变更记录 + creditsService.insertToCreditsDetail(orderByOrderNo.getAccountId(), + CreditsEventsEnum.BUY_CREDITS.getName() + "--Alipay", + CreditsEventsEnum.BUY_CREDITS.getValue(), + "positive"); + // 更新积分 + creditsService.buyCredits(orderByOrderNo.getAccountId(),orderByOrderNo.getTotalFee() / Integer.parseInt(CreditsEventsEnum.PRICE.getValue())); } } @@ -252,8 +346,8 @@ public class AliPayServiceImpl implements AliPayService { } } catch (AlipayApiException e) { - e.printStackTrace(); - throw new RuntimeException("关单接口的调用失败"); + log.error("关单失败,原因 ===> {}",e.getMessage()); + throw new BusinessException("关单接口的调用失败"); } } @@ -268,17 +362,14 @@ public class AliPayServiceImpl implements AliPayService { try { log.info("调用退款API"); - //创建退款单 RefundInfo refundInfo = refundsInfoService.createRefundByOrderNoForAliPay(orderNo, reason); - //调用统一收单交易退款接口 AlipayTradeRefundRequest request = new AlipayTradeRefundRequest (); - //组装当前业务方法的请求参数 JSONObject bizContent = new JSONObject(); bizContent.put("out_trade_no", orderNo);//订单编号 - BigDecimal refund = new BigDecimal(refundInfo.getRefund().toString()).divide(new BigDecimal("100")); + BigDecimal refund = new BigDecimal(refundInfo.getRefund().toString()); //BigDecimal refund = new BigDecimal("2").divide(new BigDecimal("100")); bizContent.put("refund_amount", refund);//退款金额:不能大于支付金额 bizContent.put("refund_reason", reason);//退款原因(可选) @@ -290,33 +381,30 @@ public class AliPayServiceImpl implements AliPayService { if(response.isSuccess()){ log.info("调用成功,返回结果 ===> " + response.getBody()); - //更新订单状态 orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.REFUND_SUCCESS); - //更新退款单 refundsInfoService.updateRefundForAliPay( refundInfo.getRefundNo(), response.getBody(), AliPayTradeStateEnum.REFUND_SUCCESS.getType()); //退款成功 + // 更新积分状态 + OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderNo); + creditsService.creditsRefund(orderByOrderNo.getAccountId(), orderByOrderNo.getTotalFee() / Integer.parseInt(CreditsEventsEnum.PRICE.getValue())); } else { log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg()); - //更新订单状态 orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.REFUND_ABNORMAL); - //更新退款单 refundsInfoService.updateRefundForAliPay( refundInfo.getRefundNo(), response.getBody(), AliPayTradeStateEnum.REFUND_ERROR.getType()); //退款失败 } - - } catch (AlipayApiException e) { e.printStackTrace(); - throw new RuntimeException("创建退款申请失败"); + throw new BusinessException("创建退款申请失败"); } } @@ -349,7 +437,7 @@ public class AliPayServiceImpl implements AliPayService { } catch (AlipayApiException e) { e.printStackTrace(); - throw new RuntimeException("查单接口的调用失败"); + throw new BusinessException("查单接口的调用失败"); } } @@ -363,7 +451,6 @@ public class AliPayServiceImpl implements AliPayService { public String queryBill(String billDate, String type) { try { - AlipayDataDataserviceBillDownloadurlQueryRequest request = new AlipayDataDataserviceBillDownloadurlQueryRequest(); JSONObject bizContent = new JSONObject(); bizContent.put("bill_type", type); @@ -383,13 +470,11 @@ public class AliPayServiceImpl implements AliPayService { return billDownloadUrl; } else { log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg()); - throw new RuntimeException("申请账单失败"); + throw new BusinessException("申请账单失败"); } - } catch (AlipayApiException e) { - e.printStackTrace(); - throw new RuntimeException("申请账单失败"); + log.error("申请账单失败,原因 ===> {}",e.getMessage()); + throw new BusinessException("申请账单失败"); } } - } diff --git a/src/main/java/com/ai/da/service/impl/CallBackServiceImpl.java b/src/main/java/com/ai/da/service/impl/CallBackServiceImpl.java new file mode 100644 index 00000000..7c5fd42f --- /dev/null +++ b/src/main/java/com/ai/da/service/impl/CallBackServiceImpl.java @@ -0,0 +1,159 @@ +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 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 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> webhookEventMap = (LinkedHashMap>) 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 getHeadersInfo(HttpServletRequest request) { + Map map = new HashMap(); + @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; + } +} diff --git a/src/main/java/com/ai/da/service/impl/CreditsServiceImpl.java b/src/main/java/com/ai/da/service/impl/CreditsServiceImpl.java new file mode 100644 index 00000000..10342ad0 --- /dev/null +++ b/src/main/java/com/ai/da/service/impl/CreditsServiceImpl.java @@ -0,0 +1,182 @@ +package com.ai.da.service.impl; + +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.mapper.primary.AccountMapper; +import com.ai.da.mapper.primary.CreditsDetailMapper; +import com.ai.da.mapper.primary.entity.Account; +import com.ai.da.mapper.primary.entity.CreditsDetail; +import com.ai.da.model.dto.QueryIncomeOrExpenditureDTO; +import com.ai.da.service.AccountService; +import com.ai.da.service.CreditsService; +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.stereotype.Service; +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.Objects; + +@Service +public class CreditsServiceImpl extends ServiceImpl implements CreditsService { + + @Resource + private AccountService accountService; + + @Resource + private AccountMapper accountMapper; + + @Override + public void initCredits() { + accountService.updateCredits(UserContext.getUserHolder().getId(), CreditsEventsEnum.INIT.getValue()); + } + + @Override + public Boolean buyCredits(Long accountId, Integer quantity) { + BigDecimal existingCredits = accountMapper.selectById(accountId).getCredits(); + BigDecimal newCredits = new BigDecimal(CreditsEventsEnum.BUY_CREDITS.getValue()).multiply(new BigDecimal(quantity)); + BigDecimal added = existingCredits.add(newCredits); + accountService.updateCredits(accountId, added.toString()); + return Boolean.TRUE; + } + + @Override + public void creditsIncrease(Long accountId, String creditsEvent) { + CreditsEventsEnum event = null; + + switch (creditsEvent) { + case "Daily Check-In": + event = CreditsEventsEnum.DAILY_CHECKIN; + break; + case "Social Media Sharing": + event = CreditsEventsEnum.SOCIAL_MEDIA_SHARING; + break; + case "Other": + event = CreditsEventsEnum.OTHER; + break; + case "Super Resolution": + event = CreditsEventsEnum.SUPER_RESOLUTION; + break; + default: + throw new BusinessException("UNKNOWN TYPE"); + } + BigDecimal existingCredits = accountMapper.selectById(accountId).getCredits(); + BigDecimal add = new BigDecimal(event.getValue()).add(existingCredits); + accountService.updateCredits(accountId, add.toString()); + } + + @Override + public void creditsDecrease(Long accountId, String creditsEvent) { + CreditsEventsEnum event; + + switch (creditsEvent) { + case "Super Resolution": + event = CreditsEventsEnum.SUPER_RESOLUTION; + break; + case "Other": + event = CreditsEventsEnum.OTHER; + break; + default: + log.error("UNKNOWN TYPE"); + throw new BusinessException("UNKNOWN TYPE"); + } + BigDecimal existingCredits = accountMapper.selectById(accountId).getCredits(); + BigDecimal subtract = existingCredits.subtract(new BigDecimal(event.getValue())); + accountService.updateCredits(accountId, subtract.toString()); + } + + @Override + public String getCredits(Long accountId) { + Account account = accountMapper.selectById(accountId); + return account.getCredits().toString(); + } + + public void creditsRefund(Long accountId, Integer quantity) { + BigDecimal existingCredits = accountMapper.selectById(accountId).getCredits(); + BigDecimal newCredits = new BigDecimal(CreditsEventsEnum.BUY_CREDITS.getValue()).multiply(new BigDecimal(quantity)); + BigDecimal subtracted = existingCredits.subtract(newCredits); + accountService.updateCredits(accountId, subtracted.toString()); + } + + /** + * 向积分变更详细表添加记录 + * + * @param changeEvent 导致积分变更的事件 + * @param credits 变更的积分 + * @param changeType 变更类型 : positive->增 negative->减 + */ + @Override + public void insertToCreditsDetail(Long accountId, String changeEvent, String credits, String changeType) { + CreditsDetail creditsDetail = new CreditsDetail(); + Account account = accountMapper.selectById(accountId); +// BigDecimal finalCredits; + String changeCredits; + if ("positive".equals(changeType)) { +// finalCredits = account.getCredits().add(new BigDecimal(credits)); + changeCredits = "+" + credits; + } else { +// 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.setCreateTime(LocalDateTime.now()); + + baseMapper.insert(creditsDetail); + } + + @Override + public PageBaseResponse queryCreditsDetailsPage(QueryIncomeOrExpenditureDTO queryPageByTimeDTO) { + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("account_id", UserContext.getUserHolder().getId()); + + String startTime = queryPageByTimeDTO.getStartTime(); + String endTime = queryPageByTimeDTO.getEndTime(); + if (StringUtil.isNullOrEmpty(startTime)) { + startTime = "2024-03-01 00:00:00"; + } + if (StringUtil.isNullOrEmpty(endTime)) { + LocalDateTime now = LocalDateTime.now(); + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + endTime = now.format(dateTimeFormatter); + } + if (!Objects.isNull(queryPageByTimeDTO.getIsIncome())) { + if (queryPageByTimeDTO.getIsIncome()) { + qw.likeRight("changed_credits", "+"); + } else { + qw.likeRight("changed_credits", "-"); + } + } + + qw.between("create_time", startTime, endTime); + qw.orderByDesc("create_time"); + Page pageInfo = new Page<>(queryPageByTimeDTO.getPage(), queryPageByTimeDTO.getSize()); + Page orderInfo = baseMapper.selectPage(pageInfo, qw); + if (CollectionUtils.isEmpty(orderInfo.getRecords())) { + return PageBaseResponse.success(new Page<>()); + } + + return PageBaseResponse.success(orderInfo); + } + + public Boolean checkCredits(Long accountId, CreditsEventsEnum event, Integer num) { + String credits = getCredits(accountId); + if (new BigDecimal(credits) + .subtract(new BigDecimal(event.getValue()) + .multiply(new BigDecimal(num))) + .compareTo(BigDecimal.ZERO) < 0) { + return Boolean.FALSE; + } + return Boolean.TRUE; + } +} diff --git a/src/main/java/com/ai/da/service/impl/GenerateServiceImpl.java b/src/main/java/com/ai/da/service/impl/GenerateServiceImpl.java index cae9902e..1fabbcc5 100644 --- a/src/main/java/com/ai/da/service/impl/GenerateServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/GenerateServiceImpl.java @@ -68,13 +68,13 @@ public class GenerateServiceImpl extends ServiceImpl i @Resource private GenerateCancelMapper generateCancelMapper; - @Value("${redis.key.consumptionOrder}") + @Value("${redis.key.orderForGenerate}") private String consumptionOrderKey; - @Value("${redis.key.cancelSet}") + @Value("${redis.key.generateCancelSet}") private String cancelSetKey; - @Value("${redis.key.exceptionMap}") + @Value("${redis.key.generateExceptionMap}") private String exceptionMapKey; @Value("${redis.key.resultMap}") @@ -368,7 +368,7 @@ public class GenerateServiceImpl extends ServiceImpl i redisUtil.addToZSet(consumptionOrderKey, uuid, maxScore); // 4、将消息发布到MQ消息队列 - rabbitMQService.publishMessage(jsonString); + rabbitMQService.publishMessageToGenerate(jsonString); // 5、返回唯一id return new PrepareForGenerateVO(uuid, 2 - trialsCount); diff --git a/src/main/java/com/ai/da/service/impl/OrderInfoServiceImpl.java b/src/main/java/com/ai/da/service/impl/OrderInfoServiceImpl.java index 8b7662f7..36f02676 100644 --- a/src/main/java/com/ai/da/service/impl/OrderInfoServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/OrderInfoServiceImpl.java @@ -1,21 +1,30 @@ package com.ai.da.service.impl; +import com.ai.da.common.context.UserContext; +import com.ai.da.common.enums.CreditsEventsEnum; import com.ai.da.common.enums.OrderStatusEnum; +import com.ai.da.common.response.PageBaseResponse; import com.ai.da.common.utils.OrderNoUtils; import com.ai.da.mapper.primary.OrderInfoMapper; import com.ai.da.mapper.primary.ProductMapper; import com.ai.da.mapper.primary.entity.OrderInfo; -import com.ai.da.mapper.primary.entity.Product; +import com.ai.da.model.dto.QueryPageByTimeDTO; +import com.ai.da.model.vo.AuthPrincipalVo; import com.ai.da.service.OrderInfoService; 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 lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; import javax.annotation.Resource; import java.time.Duration; import java.time.Instant; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; @Service @@ -25,27 +34,27 @@ public class OrderInfoServiceImpl extends ServiceImpl getOrderByPage(QueryPageByTimeDTO queryPageByTimeDTO){ + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("account_id",UserContext.getUserHolder().getId()); + + String startTime = queryPageByTimeDTO.getStartTime(); + String endTime = queryPageByTimeDTO.getEndTime(); + if (StringUtil.isNullOrEmpty(startTime)){ + startTime = "2024-02-01 00:00:00"; + } + if (StringUtil.isNullOrEmpty(endTime)){ + LocalDateTime now = LocalDateTime.now(); + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + endTime = now.format(dateTimeFormatter); + } + qw.between("create_time", startTime, endTime); + qw.orderByDesc("create_time"); + Page pageInfo = new Page<>(queryPageByTimeDTO.getPage(), queryPageByTimeDTO.getSize()); + Page orderInfo = baseMapper.selectPage(pageInfo, qw); + if (CollectionUtils.isEmpty(orderInfo.getRecords())) { + return PageBaseResponse.success(new Page<>()); + } + + return PageBaseResponse.success(orderInfo); + } + + public void updateOrderNoById(Long id, String orderNo){ + OrderInfo orderInfo = new OrderInfo(); + orderInfo.setId(id); + orderInfo.setOrderNo(orderNo); + + baseMapper.updateById(orderInfo); + } } diff --git a/src/main/java/com/ai/da/service/impl/PayPalCheckoutServiceImpl.java b/src/main/java/com/ai/da/service/impl/PayPalCheckoutServiceImpl.java new file mode 100644 index 00000000..bc7c7434 --- /dev/null +++ b/src/main/java/com/ai/da/service/impl/PayPalCheckoutServiceImpl.java @@ -0,0 +1,597 @@ +package com.ai.da.service.impl; + +import cn.hutool.core.convert.Convert; +import com.ai.da.common.config.PayPalClient; +import com.ai.da.common.config.exception.BusinessException; +import com.ai.da.common.constant.PayPalCheckoutConstant; +import com.ai.da.common.enums.*; +import com.ai.da.common.utils.RedisUtil; +import com.ai.da.common.utils.paypalRequest.AuthenticationRequest; +import com.ai.da.common.utils.paypalRequest.WebhookVerifyRequest; +import com.ai.da.mapper.primary.entity.OrderInfo; +import com.ai.da.mapper.primary.entity.RefundInfo; +import com.ai.da.service.*; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +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 com.paypal.http.exceptions.SerializeException; +import com.paypal.http.serializer.Json; +import com.paypal.orders.*; +import com.paypal.payments.CapturesGetRequest; +import com.paypal.payments.CapturesRefundRequest; +import com.paypal.payments.RefundRequest; +import com.paypal.payments.RefundsGetRequest; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +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.*; + +import static com.ai.da.common.constant.PayPalCheckoutConstant.*; + +@Slf4j +@Service +//@RefreshScope +public class PayPalCheckoutServiceImpl implements PayPalCheckoutService { + + @Value("${paypal.receiver.email}") + private String receiverEmail; + + @Value("${paypal.client-id}") + private String clientId; + + @Value("${paypal.client-secret}") + private String clientSecret; + + @Resource + private PayPalClient payPalClient; + @Resource + private OrderInfoService orderInfoService; + @Resource + private PaymentInfoService paymentInfoService; + @Resource + private RefundInfoService refundsInfoService; + @Resource + private CreditsService creditsService; + @Resource + private RedisUtil redisUtil; + + /** + * 创建订单的方法 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public HashMap createOrder(Integer amount, String returnUrl) throws SerializeException { + // 生成订单 + log.info("生成订单"); + OrderInfo orderInfo = orderInfoService.createOrderByProductId(amount, PayTypeEnum.PAYPAL.getType()); + + OrdersCreateRequest request = new OrdersCreateRequest(); + request.header("prefer", "return=representation"); + request.requestBody(buildRequestBody(String.valueOf(orderInfo.getTotalFee()), returnUrl)); + HttpResponse response = null; + try { + response = payPalClient.client(MODE, clientId, clientSecret).execute(request); + } catch (Exception e) { + log.error("调用paypal订单创建失败,失败原因 ===> {}", e.getMessage()); + throw new BusinessException("Order creation failed"); + } + + String approve = ""; + assert response != null; + if (response.statusCode() == 201) { + log.info("Status Code = {}, Status = {}, OrderID = {}, Intent = {}", response.statusCode(), response.result().status(), response.result().id(), response.result().checkoutPaymentIntent()); + for (LinkDescription link : response.result().links()) { + log.info("Links-{}: {} \tCall Type: {}", link.rel(), link.href(), link.method()); + if (link.rel().equals("approve")) { + approve = link.href(); + } + } + String totalAmount = response.result().purchaseUnits().get(0).amountWithBreakdown().currencyCode() + ":" + response.result().purchaseUnits().get(0).amountWithBreakdown().value(); + log.info("Total Amount: {}", totalAmount); + String json = new JSONObject(new Json().serialize(response.result())).toString(4); + log.info("createOrder response body: {}", json); + } + + String orderId = response.result().id(); + orderInfoService.updateOrderNoById(orderInfo.getId(), orderId); + + HashMap returnData = new HashMap<>(); + returnData.put("approve", approve); + returnData.put("orderNo", orderId); + + // 需要返回地址和订单id + return returnData; + } + + // ##Validate Webhook + public Boolean doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + try { + String body = getBody(req); + Map webhookEvent = new ObjectMapper().readValue(body, Map.class); + + HashMap 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(getOAuth()); + webhookVerifyRequest.requestBody(webhookRequest); + // 验签 + HttpResponse 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> webhookEventMap = (LinkedHashMap>) webhookEvent; + String orderId = webhookEventMap.get("resource").get("id"); + processOrder(orderId); + return Boolean.TRUE; + } + } else { + log.error("Paypal 验签失败"); + } + } 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 getHeadersInfo(HttpServletRequest request) { + Map map = new HashMap(); + @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; + } + + /** + * 生成订单主体信息 + */ + private OrderRequest buildRequestBody(String price, String returnUrl) { + OrderRequest orderRequest = new OrderRequest(); + orderRequest.checkoutPaymentIntent(CAPTURE); + + ApplicationContext applicationContext = new ApplicationContext() + .brandName(BRANDNAME) + .landingPage(LANDINGPAGE) + .cancelUrl(returnUrl).returnUrl(returnUrl) + .userAction(USERACTION) + .shippingPreference(SHIPPINGPREFERENCE); + orderRequest.applicationContext(applicationContext); + + List purchaseUnitRequests = new ArrayList(); + + PurchaseUnitRequest purchaseUnitRequest = new PurchaseUnitRequest() + .amountWithBreakdown(new AmountWithBreakdown() + .amountBreakdown(new AmountBreakdown()) + .currencyCode(CurrencyCodesEnum.HONG_KONG_DOLLAR.getCode()) + .value(price)); + purchaseUnitRequests.add(purchaseUnitRequest); + orderRequest.purchaseUnits(purchaseUnitRequests); + return orderRequest; + } + + /*@Override + public String callback(@SuppressWarnings("rawtypes") Map map) { + + log.info("paypal支付通知正在执行"); + log.info("通知参数 ===> {}", map); + +// log.info(map.toString()); + String outTradeNo = (String) map.get("invoice"); + String paymentStatus = (String) map.get("payment_status"); + String amount = (String) map.get("mc_gross"); + String currency = (String) map.get("mc_currency"); + String paymentId = (String) map.get("txn_id"); + String parentPaymentId = (String) map.get("parent_txn_id"); + log.info("商家订单号 = {}", outTradeNo); + log.info("订单状态 = {}", paymentStatus); + log.info("金额 = {}", amount); + log.info("币种 = {}", currency); + log.info("流水号 = {}", paymentId); + log.info("父流水号 = {}", parentPaymentId); + + if (!receiverEmail.equals(map.get("receiver_email"))) { + log.info("FAIL = 商户id错误, outTradeNo = {}", outTradeNo); + return "failure"; + } + if ("Completed".equals(paymentStatus)) { + //进行数据库操作 + // + // + log.info("支付成功,状态为=COMPLETED"); + return "success"; + } + if ("Refunded".equals(paymentStatus)) { + //进行数据库操作 + // + // + log.info("退款成功"); + return "success"; + } + if ("Pending".equals(paymentStatus) && StringUtils.isEmpty(parentPaymentId)) { + String pendingReason = String.valueOf(map.get("pending_reason")); + //进行数据库操作 + // + // + log.info("订单支付成功,状态为=PENDING,产生此状态的原因是 {}", pendingReason); + return "success"; + } + if (StringUtils.isEmpty(parentPaymentId)) { + if (PayPalCheckoutConstant.PAYMENT_STATUS_REVERSED.equals(paymentStatus) + || PayPalCheckoutConstant.PAYMENT_STATUS_CANCELED_REVERSAL.equals(paymentStatus) + || PayPalCheckoutConstant.PAYMENT_STATUS_DENIED.equals(paymentStatus)) { + String reasonCode = String.valueOf(map.get("reason_code")); + //进行数据库操作(状态修改) + // + // + log.info("订单异常,请尽快查看处理,状态为={},产生此状态的原因是 {} ", paymentStatus, reasonCode); + return PayPalCheckoutConstant.SUCCESS; + } + if (PayPalCheckoutConstant.PAYMENT_STATUS_EXPIRED.equals(paymentStatus) + || PayPalCheckoutConstant.PAYMENT_STATUS_CREATED.equals(paymentStatus) + || PayPalCheckoutConstant.PAYMENT_STATUS_FAILED.equals(paymentStatus) + || PayPalCheckoutConstant.PAYMENT_STATUS_PROCESSED.equals(paymentStatus) + || PayPalCheckoutConstant.PAYMENT_STATUS_VOIDED.equals(paymentStatus)) { + //进行数据库操作(状态修改) + // + // + log.info("其他订单状态,订单异常,请尽快查看处理, 状态={}", paymentStatus); + return PayPalCheckoutConstant.SUCCESS; + } + } + return "failure"; + }*/ + + /** + * 查询订单信息 + * + * @param orderNo + * @return + * @throws SerializeException + */ + public String queryOrder(String orderNo) throws SerializeException { + OrdersGetRequest request = new OrdersGetRequest(orderNo); + + HttpResponse response = null; + try { + response = payPalClient.client(MODE, clientId, clientSecret).execute(request); + } catch (Exception e) { + log.error("paypal订单查询失败,失败原因 ===> {}", e.getMessage()); + } + System.out.println("Status Code: " + response.statusCode()); + System.out.println("Status: " + response.result().status()); + System.out.println("Order id: " + response.result().id()); + if (response.result().purchaseUnits().get(0).payments() != null) { + List captures = response.result().purchaseUnits().get(0).payments().captures(); + if (captures != null) { + for (Capture capture : captures) { + System.out.println("\t订单编号= " + capture.invoiceId() + "\tCapture Id= " + capture.id() + "\tCapture status= " + capture.status() + "\tCapture amount= " + capture.amount().currencyCode() + ":" + capture.amount().value()); + } + } + List refunds = response.result().purchaseUnits().get(0).payments().refunds(); + if (refunds != null) { + for (Refund refund : refunds) { + System.out.println("\t售后编号= " + refund.invoiceId() + "\tRefund Id= " + refund.id() + "\tRefund status= " + refund.status() + "\tRefund amount= " + refund.amount().currencyCode() + ":" + refund.amount().value()); + } + } + } + System.out.println("Links: "); + for (com.paypal.orders.LinkDescription link : response.result().links()) { + System.out.println("\t" + link.rel() + ": " + link.href() + "\tCall Type: " + link.method()); + } + + System.out.println("Full response body:"); + String json = new JSONObject(new Json().serialize(response.result())).toString(4); + System.out.println(json); + return null; + } + + /** + * 用户授权支付成功,进行扣款操作 + */ + @Transactional(rollbackFor = Exception.class) + public Order captureOrder(String orderId) { + OrdersCaptureRequest request = new OrdersCaptureRequest(orderId); + request.requestBody(new OrderRequest()); + PayPalClient payPalClient = new PayPalClient(); + HttpResponse response; + + try { + response = payPalClient.client(MODE, clientId, clientSecret).execute(request); + } catch (Exception e) { + log.error("调用paypal扣款失败,失败原因 ===> {}", e.getMessage()); + throw new BusinessException("Order deduction failed."); + } + log.info("Status Code = {}, Status = {}, OrderID = {}", response.statusCode(), response.result().status(), response.result().id()); + for (LinkDescription link : response.result().links()) { + log.info("Links-{}: {} \tCall Type: {}", link.rel(), link.href(), link.method()); + } + for (PurchaseUnit purchaseUnit : response.result().purchaseUnits()) { + for (Capture capture : purchaseUnit.payments().captures()) { + log.info("Capture id: {}", capture.id()); + log.info("status: {}", capture.status()); + log.info("invoice_id: {}", capture.invoiceId()); + if ("COMPLETED".equals(capture.status())) { + //进行数据库操作,修改订单状态为已支付成功,尽快发货(配合回调和CapturesGet查询确定成功) + log.info("支付成功,状态为=COMPLETED"); + } + if ("PENDING".equals(capture.status())) { + log.info("status_details: {}", capture.captureStatusDetails().reason()); + String reason = "PENDING"; + if (capture.captureStatusDetails() != null && capture.captureStatusDetails().reason() != null) { + reason = capture.captureStatusDetails().reason(); + } + //进行数据库操作,修改订单状态为已支付成功,但触发了人工审核,请审核通过后再发货(配合回调和CapturesGet查询确定成功) + log.info("支付成功,状态为=PENDING : {}", reason); + } + } + } + Payer buyer = response.result().payer(); + log.info("Buyer Email Address: {}", buyer.email()); + log.info("Buyer Name: {} {}", buyer.name().givenName(), buyer.name().surname()); +// String jsonString = JSON.toJSONString(response.result()); + String json = null; + try { + json = new JSONObject(new Json().serialize(response.result())).toString(4); + } catch (SerializeException e) { + log.warn("response序列化出错,具体信息 ===> {}", e.getMessage()); + } + log.info("captureOrder response body: {}", json); +// return Convert.toStr(new Json().serialize(response.result())); + return response.result(); + } + + /** + * 查询扣款 + */ + public String queryCapture(String orderNo) throws IOException { + CapturesGetRequest request = new CapturesGetRequest("扣款id, CaptureOrder生成"); + + HttpResponse 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()); + System.out.println("Links: "); + for (com.paypal.payments.LinkDescription link : response.result().links()) { + System.out.println("\t" + link.rel() + ": " + link.href() + "\tCall Type: " + link.method()); + } + System.out.println("Full response body:"); + System.out.println(new JSONObject(new Json().serialize(response.result())).toString(4)); + return null; + } + + + /** + * 申请退款 + */ + @Transactional(rollbackFor = Exception.class) + public Boolean refundOrder(String orderId, String reason) throws IOException { + + RefundInfo refundByOrderNo = refundsInfoService.createRefundByOrderNo(orderId, reason); + + OrdersGetRequest ordersGetRequest = new OrdersGetRequest(orderId); + PayPalClient payPalClient = new PayPalClient(); + HttpResponse ordersGetResponse = null; + ordersGetRequest.authorization("Bearer " + getOAuth()); + boolean result; + try { + ordersGetResponse = payPalClient.client(MODE, clientId, clientSecret).execute(ordersGetRequest); + } catch (Exception e) { + log.error("调用paypal订单查询失败,失败原因 ===> {}", e.getMessage()); + throw new BusinessException("Order query failed"); + } + String captureId = ordersGetResponse.result().purchaseUnits().get(0).payments().captures().get(0).id(); + CapturesRefundRequest request = new CapturesRefundRequest(captureId); + request.authorization("Bearer " + getOAuth()); + request.prefer("return=representation"); + + OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderId); + request.requestBody(buildRefundRequestBody(String.valueOf(orderInfo.getTotalFee()), reason)); + HttpResponse response = null; + try { + response = payPalClient.client(MODE, clientId, clientSecret).execute(request); + } catch (IOException e) { + log.error("调用paypal退款申请失败,失败原因 {}", e.getMessage()); + throw new BusinessException("Request for refund failed"); + } + + log.info("Status Code = {}, Status = {}, RefundID = {}", response.statusCode(), response.result().status(), response.result().id()); + if ("COMPLETED".equals(response.result().status())) { + //进行数据库操作,修改状态为已退款(配合回调和退款查询确定退款成功) + + //更新订单状态 + orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.REFUND_SUCCESS); + + refundsInfoService.updateRefundForPayPal( + refundByOrderNo.getId(), + response.result().id(), + new Gson().toJson(response.result(), com.paypal.payments.Refund.class), + AliPayTradeStateEnum.REFUND_SUCCESS.getType()); //退款成功 + + // 更新积分状态 + OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderId); + creditsService.creditsRefund(orderByOrderNo.getAccountId(), orderByOrderNo.getTotalFee() / Integer.parseInt(CreditsEventsEnum.PRICE.getValue())); + log.info("退款成功"); + result = Boolean.TRUE; + } else { + //更新订单状态 + orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.REFUND_ABNORMAL); + + //更新退款单 + refundsInfoService.updateRefundForPayPal( + refundByOrderNo.getId(), + response.result().id(), + new Gson().toJson(response.result(), com.paypal.payments.Refund.class), + AliPayTradeStateEnum.REFUND_ERROR.getType()); //退款失败 + result = Boolean.FALSE; + } + for (com.paypal.payments.LinkDescription link : response.result().links()) { + log.info("Links-{}: {} \tCall Type: {}", link.rel(), link.href(), link.method()); + } + String json = new JSONObject(new Json().serialize(response.result())).toString(4); + log.info("refundOrder response body: {}", json); + return result; + } + + public RefundRequest buildRefundRequestBody(String price, String reason) { + RefundRequest refundRequest = new RefundRequest(); + com.paypal.payments.Money money = new com.paypal.payments.Money(); + money.currencyCode(CurrencyCodesEnum.HONG_KONG_DOLLAR.getCode()); + money.value(price); + refundRequest.amount(money); +// refundRequest.invoiceId("P2020052514440001"); + refundRequest.noteToPayer(reason); + try { + log.info("refund order body : {}", Convert.toStr(new Json().serialize(refundRequest))); + } catch (SerializeException e) { + throw new RuntimeException(e); + } + + return refundRequest; + } + + /** + * 查询退款 + */ + public String queryRefund(String orderNo) throws IOException { + RefundsGetRequest request = new RefundsGetRequest("退款id RefundOrder生成"); + HttpResponse 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()); + System.out.println("Links: "); + for (com.paypal.payments.LinkDescription link : response.result().links()) { + System.out.println("\t" + link.rel() + ": " + link.href() + "\tCall Type: " + link.method()); + } + System.out.println("Full response body:"); + System.out.println(new JSONObject(new Json().serialize(response.result())).toString(4)); + return null; + } + + public String getOAuth() { + // 1、判断缓存区是否有该token + Boolean hasKey = redisUtil.hasKey(PAYPAL_TOKEN_KEY); + if (hasKey) { + return redisUtil.getFromString(PAYPAL_TOKEN_KEY); +// return "A21AAKnpozur9r9omqQ2ge5aXHBBdEERi5F8FIgNYOjhhO2N7rjmkz2irh2lScpBO3s3Cqukw3eZkpYZ4YWE7rIacjv7MHmow"; + } + + // 2、无或者过期,重新获取token,返回 + AuthenticationRequest authenticationRequest = new AuthenticationRequest(); + authenticationRequest.authorization(clientId, clientSecret); + try { + HttpResponse 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 + redisUtil.addToString(PAYPAL_TOKEN_KEY, accessToken, expiresIn); + return accessToken; + } catch (IOException e) { + log.error("获取paypal token失败,失败原因 ===> {}", e.getMessage()); + throw new BusinessException(e); + } + } + + // 处理当前订单 + @Transactional(rollbackFor = Exception.class) + public void processOrder(String orderId) { + // 1、确定当前订单是否已经被扣款 + OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderId); + if (orderInfo.getOrderStatus().equals(OrderStatusEnum.SUCCESS.getType())) { + // 直接返回 + return; + } + // 发起扣款请求 + Order capturedOrder = captureOrder(orderId); + // 业务处理 + if (PayPalOrderStatusEnum.COMPLETED.getStatus().equals(capturedOrder.status())) { + //更新订单状态 + orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.SUCCESS); + //记录支付日志 + paymentInfoService.createPaymentInfoForPayPal(capturedOrder); + // 添加积分变更记录 + creditsService.insertToCreditsDetail(orderInfo.getAccountId(), + CreditsEventsEnum.BUY_CREDITS.getName() + "--PayPal", + CreditsEventsEnum.BUY_CREDITS.getValue(), + "positive"); + // 更新积分 + creditsService.buyCredits(orderInfo.getAccountId(), orderInfo.getTotalFee() / Integer.parseInt(CreditsEventsEnum.PRICE.getValue())); + } + } +} + + diff --git a/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java b/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java index 31cca80a..367dd376 100644 --- a/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java @@ -6,6 +6,7 @@ import com.ai.da.mapper.primary.entity.PaymentInfo; import com.ai.da.service.PaymentInfoService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.google.gson.Gson; +import com.paypal.orders.Order; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -70,8 +71,8 @@ public class PaymentInfoServiceImpl extends ServiceImpl queryWrapper = new QueryWrapper<>(); +// queryWrapper.eq("order_no", orderNo); + + //设置要修改的字段 + RefundInfo refundInfo = new RefundInfo(); + refundInfo.setId(id); + refundInfo.setRefundNo(null); + refundInfo.setRefundId(refundId); + refundInfo.setRefundStatus(refundStatus);//退款状态 + refundInfo.setContentReturn(content);//将全部响应结果存入数据库的content字段 + + //更新退款单 +// baseMapper.update(refundInfo, queryWrapper); + baseMapper.updateById(refundInfo); + } } diff --git a/src/main/java/com/ai/da/service/impl/SuperResolutionServiceImpl.java b/src/main/java/com/ai/da/service/impl/SuperResolutionServiceImpl.java new file mode 100644 index 00000000..c9ecdec7 --- /dev/null +++ b/src/main/java/com/ai/da/service/impl/SuperResolutionServiceImpl.java @@ -0,0 +1,204 @@ +package com.ai.da.service.impl; + +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.TaskListMapper; +import com.ai.da.mapper.primary.entity.TaskList; +import com.ai.da.model.dto.QueryTaskHistoryDTO; +import com.ai.da.model.dto.SuperResolutionDTO; +import com.ai.da.model.dto.TaskDTO; +import com.ai.da.python.PythonService; +import com.ai.da.service.CreditsService; +import com.ai.da.service.RabbitMQService; +import com.ai.da.service.SuperResolutionService; +import com.ai.da.service.TaskListService; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import io.netty.util.internal.StringUtil; +import lombok.extern.slf4j.Slf4j; +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.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Slf4j +@Service +public class SuperResolutionServiceImpl extends ServiceImpl implements SuperResolutionService { + + public static final Integer creditsConsumption = 100; + + @Resource + private CreditsService creditsService; + @Resource + private RabbitMQService rabbitMQService; + @Resource + private PythonService pythonService; + + @Resource + private TaskListService taskListService; + + @Resource + private RedisUtil redisUtil; + + @Value("${redis.key.orderForSR}") + private String orderForSR; + + @Value("${redis.key.resultMap}") + private String resultMapKey; + + @Value("${minio.bucketName.users}") + private String usersBucket; + + @Value("${minio.endpoint}") + private String endpoint; + + @Override + @Transactional(rollbackFor = Exception.class) + public List prepareForSR(List 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 { + throw new BusinessException("Not enough Credits"); + } + + ArrayList uuidList = new ArrayList<>(); + + for (SuperResolutionDTO superResolutionDTO : superResolutionDTOList) { + // 2、生成唯一id 使用uuid + String uuid = UUID.randomUUID().toString(); + int num = 1; + // 判断已经正常生成结果的uuid或正在排队的uuid中是否有相同的id + while ((redisUtil.isElementExistsInMap(resultMapKey, uuid) || + redisUtil.isElementExistsInZSet(orderForSR, uuid)) + && num < 10) { + uuid = UUID.randomUUID().toString(); + num++; + } + uuid += "-" + accountId; + uuidList.add(uuid); + + // 3、截取minio地址 (前提是该url是由minio地址转换来的) + String inputString = superResolutionDTO.getImages(); + String replace = inputString.replace(endpoint + "/", ""); + String minioPath = replace.substring(0, replace.indexOf("?")); +// log.info(minioPath); + superResolutionDTO.setImages(minioPath); + + superResolutionDTO.setUniqueId(uuid); + String jsonString = JSON.toJSONString(superResolutionDTO); + + // 4、将数据存到数据库 + TaskList taskList = new TaskList(); + taskList.setAccountId(accountId); + taskList.setTaskType("SR"); + taskList.setInputUrl(superResolutionDTO.getImages()); + taskList.setScale(superResolutionDTO.getScale()); + taskList.setStatus("Waiting"); + taskList.setTaskId(superResolutionDTO.getUniqueId()); + taskList.setCreateTime(LocalDateTime.now()); + + baseMapper.insert(taskList); + + // 5、加入任务列表 设置状态为 等待中 + 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消息队列 + log.info("发送消息到SR_QUEUE,参数 :{}", jsonString); + rabbitMQService.publishMessageToSR(jsonString); + } + + // 6、返回唯一id列表 + return uuidList; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void SR(SuperResolutionDTO superResolutionDTO) throws Exception { + // 1、向模型发起请求 + pythonService.superResolution(superResolutionDTO); + + // 2、更新状态 + updateSROutput(superResolutionDTO.getUniqueId(), "Executing", null); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void setSRResult(String taskId, String output, String status) { + Long accountId = Long.parseLong(taskId.substring(taskId.lastIndexOf("-") + 1)); + // 获取结果后更新 + updateSROutput(taskId, status, output); + + if ("success".equals(status)) { + // 3、记录积分变更 + creditsService.insertToCreditsDetail(accountId, + CreditsEventsEnum.SUPER_RESOLUTION.getName(), + CreditsEventsEnum.SUPER_RESOLUTION.getValue(), + "negative"); + } else { + // 将之前扣除的积分返还 + creditsService.creditsIncrease(accountId, CreditsEventsEnum.SUPER_RESOLUTION.getName()); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateSROutput(String taskId, String status, String output) { + UpdateWrapper uw = new UpdateWrapper<>(); + uw.eq("task_id", taskId); + + uw.set("output_url", output); + uw.set("status", status); + baseMapper.update(null, uw); + + } + + // 分页查询 + @Override + public PageBaseResponse getTaskHistoryPage(QueryTaskHistoryDTO queryTaskHistoryDTO) { + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("account_id", UserContext.getUserHolder().getId()); + + String startTime = queryTaskHistoryDTO.getStartTime(); + String endTime = queryTaskHistoryDTO.getEndTime(); + if (StringUtil.isNullOrEmpty(startTime)) { + startTime = "2024-03-01 00:00:00"; + } + if (StringUtil.isNullOrEmpty(endTime)) { + LocalDateTime now = LocalDateTime.now(); + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + endTime = now.format(dateTimeFormatter); + } + + qw.between("create_time", startTime, endTime); + qw.orderByDesc("create_time"); + Page pageInfo = new Page<>(queryTaskHistoryDTO.getPage(), queryTaskHistoryDTO.getSize()); + Page orderInfo = baseMapper.selectPage(pageInfo, qw); + if (CollectionUtils.isEmpty(orderInfo.getRecords())) { + return PageBaseResponse.success(new Page<>()); + } + + return PageBaseResponse.success(orderInfo); + } +} diff --git a/src/main/java/com/ai/da/service/impl/TaskListServiceImpl.java b/src/main/java/com/ai/da/service/impl/TaskListServiceImpl.java new file mode 100644 index 00000000..d8e02623 --- /dev/null +++ b/src/main/java/com/ai/da/service/impl/TaskListServiceImpl.java @@ -0,0 +1,123 @@ +package com.ai.da.service.impl; + +import com.ai.da.common.context.UserContext; +import com.ai.da.common.response.PageBaseResponse; +import com.ai.da.common.utils.MinioUtil; +import com.ai.da.common.utils.RedisUtil; +import com.ai.da.mapper.primary.TaskListMapper; +import com.ai.da.mapper.primary.entity.TaskList; +import com.ai.da.model.dto.QueryTaskHistoryDTO; +import com.ai.da.model.dto.SuperResolutionDTO; +import com.ai.da.model.dto.TaskDTO; +import com.ai.da.model.vo.TaskVO; +import com.ai.da.service.SuperResolutionService; +import com.ai.da.service.TaskListService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import io.netty.util.internal.StringUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.lang.reflect.Type; +import java.time.format.DateTimeFormatter; +import java.util.*; + +@Service +@Slf4j +public class TaskListServiceImpl extends ServiceImpl implements TaskListService { + + @Resource + private RedisUtil redisUtil; + + @Value("${redis.key.taskList}") + private String taskListKey; + + @Resource + private MinioUtil minioUtil; + + @Resource + private SuperResolutionService superResolutionService; + + @Override + @Transactional(rollbackFor = Exception.class) + public List> getExecTask(List taskIdList) { + Long id = UserContext.getUserHolder().getId(); +// Set keys = redisUtil.getKeysFromString(taskListKey + ":" + id + ":*"); + ArrayList> taskDTOS = new ArrayList<>(); + taskIdList.forEach(taskId -> { + String taskDetail = redisUtil.getFromString(taskListKey + ":" + id + ":" + taskId); + Gson gson = new Gson(); + Type type = new TypeToken>() { + }.getType(); + TaskDTO taskDTO = gson.fromJson(taskDetail, type); + if (Objects.isNull(taskDTO)) { + // 未执行成功的taskId在redis被删除,将该条数据在db中的状态改为失败 + superResolutionService.updateSROutput(taskId, "fail", null); + 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)); + taskDTOS.add(taskDTO); + } + }); + return taskDTOS; + } + + public void addToTaskListRedis(TaskDTO taskDTO) { + String key = taskListKey + ":" + UserContext.getUserHolder().getId() + ":" + taskDTO.getTaskId(); + redisUtil.addToString(key, new Gson().toJson(taskDTO), 24L * 60 * 60 * 3); + } + + // 3、更新任务状态 + @Override + public void updateTaskStatusOrOutputRedis(String taskId, String status, String output) { + String key = taskListKey + ":" + taskId.substring(taskId.lastIndexOf("-") + 1) + ":" + taskId; + String taskDetail = redisUtil.getFromString(key); + Gson gson = new Gson(); + Type type = new TypeToken>() { + }.getType(); + TaskDTO taskDTO = gson.fromJson(taskDetail, type); + taskDTO.setStatus(status); + if (!StringUtil.isNullOrEmpty(output)) { + taskDTO.setOutputImage(output); + } + Long expire = redisUtil.getExpire(key); + redisUtil.addToString(key, new Gson().toJson(taskDTO), expire); + } + + @Override + public PageBaseResponse getAllTask(QueryTaskHistoryDTO queryTaskHistoryDTO) { + PageBaseResponse response = new PageBaseResponse<>(); + ArrayList taskLists = new ArrayList<>(); + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + switch (queryTaskHistoryDTO.getType()) { + case "SR": + PageBaseResponse taskHistoryPage = superResolutionService.getTaskHistoryPage(queryTaskHistoryDTO); + List srHistory = taskHistoryPage.getContent(); + srHistory.forEach(s -> { + // 成功失败的都返回 + 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.setStatus(s.getStatus()); + task.setTaskId(s.getTaskId()); + task.setCreateDate(s.getCreateTime().format(dateTimeFormatter)); + task.setOtherInput("×" + s.getScale()); + taskLists.add(task); + }); + BeanUtils.copyProperties(taskHistoryPage, response); + response.setContent(taskLists); + break; + case "GENERATE": + log.info("未知任务类型--GENERATE"); + } + return response; + } +} diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 65a89b82..4c8d990c 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -21,7 +21,7 @@ spring.security.jwtExpiration=8640000000 #spring security权限设置 认证了token还要认证权限 不然报错Full authentication is required to access this resource spring.security.ignorePaths=/,/favicon.ico,/doc.html,/webjars/**,/swagger-resources,/v2/api-docs,\ /api/account/**,/api/element/**,/api/python/**,/api/design/**,/api/history/**,/api/library/**,/api/third/party/**,/api/generate/**,/api/workspace/**,/api/classification/**,\ - /api/product/**,/api/ali-pay/**,/api/order-info/** + /api/product/**,/api/ali-pay/**,/api/order-info/**,/api/paypal/**,/api/credits/**,/api/inquiry/**,/api/tasks/**,/api/python/prepareForSR spring.security.authApi=/auth/login @@ -46,6 +46,7 @@ spring.servlet.multipart.max-request-size= 10MB #访问python服务的ip(对应环境) access.python.ip=http://18.167.251.121 access.python.port=9992 +access.python.sr=http://18.167.251.121:9994 minio.endpoint=https://www.minio.aida.com.hk:9000 minio.accessKey=admin @@ -74,7 +75,11 @@ spring.redis.lettuce.pool.max-idle=8 spring.redis.lettuce.pool.min-idle=0 spring.redis.lettuce.pool.max-wait=5 -redis.key.consumptionOrder=ConsumptionOrder -redis.key.cancelSet=CancelSet -redis.key.exceptionMap=ExceptionMap -redis.key.resultMap=ResultMap \ No newline at end of file +redis.key.orderForGenerate=OrderForGenerate +redis.key.generateCancelSet=GenerateCancelSet +redis.key.generateExceptionMap=GenerateExceptionMap +redis.key.resultMap=ResultMap +redis.key.orderForSR=OrderForSR +redis.key.SRCancelSet=SRCancelSet +redis.key.SRExceptionMap=SRExceptionMap +redis.key.taskList=TaskList \ No newline at end of file diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 6a2b9d4d..af7d6688 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -15,7 +15,7 @@ spring.security.jwtTokenPrefix=Bearer- spring.security.jwtExpiration=8640000000 #spring security权限设置 认证了token还要认证权限 不然报错Full authentication is required to access this resource spring.security.ignorePaths=/,/favicon.ico,/doc.html,/webjars/**,/swagger-resources,/v2/api-docs,\ - /api/account/**,/api/element/**,/api/python/**,/api/design/**,/api/history/**,/api/library/**,/api/third/party/**,/api/generate/**,/api/workspace/**,/api/classification/** + /api/account/**,/api/element/**,/api/python/**,/api/design/**,/api/history/**,/api/library/**,/api/third/party/**,/api/generate/**,/api/workspace/**,/api/classification/**,/api/inquiry/** spring.security.authApi=/auth/login diff --git a/src/main/resources/mapper/primary/DesignMapper.xml b/src/main/resources/mapper/primary/DesignMapper.xml index b71f0f60..edf3f675 100644 --- a/src/main/resources/mapper/primary/DesignMapper.xml +++ b/src/main/resources/mapper/primary/DesignMapper.xml @@ -26,4 +26,18 @@ #{createDate}); + + diff --git a/src/main/resources/messages_en.properties b/src/main/resources/messages_en.properties index ca17378d..39096842 100644 --- a/src/main/resources/messages_en.properties +++ b/src/main/resources/messages_en.properties @@ -169,6 +169,8 @@ generate.interface.error=We encountered an error with the generate interface. (P chat-bot.interface.exception=We encountered an error with the chat robot interface.(Please try again later. If this issue persists, please contact us at help@aida.com.hk.) compose-layer.interface.exception=We encountered issues while flattening the layers.(Please try again later. If this issue persists, please contact us at help@aida.com.hk.) cloth-classification.interface.exception=We encountered some issues while obtaining clothing categories.(Please try again later. If this issue persists, please contact us at help@aida.com.hk.) +sr.interface.error=We encountered an error with the super resolution. (Please try again later. If this issue persists, please contact us at help@aida.com.hk.) + # 多语言返回 OVERALL=Overall diff --git a/src/main/resources/paypal-sandbox.properties b/src/main/resources/paypal-sandbox.properties new file mode 100644 index 00000000..aea3ac83 --- /dev/null +++ b/src/main/resources/paypal-sandbox.properties @@ -0,0 +1,20 @@ +# developer-sandbox +#paypal.client-id=ATbaebYi7-GXWRWJqwRLYMzKEbwjh4BFRqD4Y13i4lZq0rplWIM_IpPrtPKpdkAt_KrPXd6IJTwsDqa5 +#paypal.client-secret=EHWWJqGmmbfjLXqCUpGrvxRYBPPtWvA3hR5ZaAyHlGSVJiHoQPS8skbNaJ9h39VObnchUbgiY2pPu__s +#paypal.receiver.email=sb-ukxfk29608925@business.example.com +#paypal.mode=sandbox +#paypal.webhook_id=31797347YC028794L + +# aida-sandbox +#paypal.client-id=AbDDH8jnTrKqjnWLFgEu6LogYzVz2ZLuirE4W54t1M4lrofrP5OzXfhbxqktLLFB-rAO9KeYQVYFJ_tO +#paypal.client-secret=EOOoiIAe_dyR2YhY7qCIqWipZvYXCDrmBlFYchphuvkPFms1spsBGTlStlrx580y4hN-EukWwF9m_LAs +#paypal.receiver.email=sb-4xe8i29784722@business.example.com +#paypal.mode=sandbox +#paypal.webhook_id=1WH327112B602422N + +# aida-live +paypal.client-id=ASWSIZ3MXJU5w5VOeOHeigWcSw6iinl30ZCipruziKpHclxP0ryf8-7VKG1Ba2VwZwa2DMvGEzTfCTgz +paypal.client-secret=EHQg_K5PSqmp4FJlzEcOEH_kFkmq4aBzaI7jridw53L6cOQRULBAnfv2KakRfrsqaU1PDSkO4Co9Vyxc +paypal.receiver.email=kimwong@code-create.com.hk +paypal.mode=live +paypal.webhook_id=41L14847MC833625B \ No newline at end of file