diff --git a/pom.xml b/pom.xml index e20e83e1..db7bdb0b 100644 --- a/pom.xml +++ b/pom.xml @@ -151,9 +151,15 @@ 3.0.3 + + + + + + com.tencentcloudapi - tencentcloud-sdk-java-ses + tencentcloud-sdk-java 3.1.572 @@ -288,6 +294,28 @@ spring-boot-starter-websocket + + com.google.auth + google-auth-library-oauth2-http + 1.8.0 + + + + com.google.api-client + google-api-client + 1.32.1 + + + com.google.oauth-client + google-oauth-client + 1.32.1 + + + com.google.http-client + google-http-client-jackson2 + 1.41.5 + + diff --git a/src/main/java/com/ai/da/common/config/MyTaskScheduler.java b/src/main/java/com/ai/da/common/config/MyTaskScheduler.java index 7eb5d0d2..3e73536e 100644 --- a/src/main/java/com/ai/da/common/config/MyTaskScheduler.java +++ b/src/main/java/com/ai/da/common/config/MyTaskScheduler.java @@ -51,7 +51,7 @@ public class MyTaskScheduler { // 定时任务,每十五天执行一次 // @Scheduled(cron = "0 0 0 ? * MON") - @Scheduled(cron = "0 0 0 */15 * ?") +// @Scheduled(cron = "0 0 0 */15 * ?") public void checkExpiry() { // 检测正式用户是否快要过期 QueryWrapper qw = new QueryWrapper<>(); @@ -85,7 +85,7 @@ public class MyTaskScheduler { } } } - @Scheduled(cron = "0 0 9 * * ?") +// @Scheduled(cron = "0 0 9 * * ?") public void sendTrialOrderExcelToManagements() { // 获取前一天日期 LocalDate yesterday = LocalDate.now().minusDays(1); diff --git a/src/main/java/com/ai/da/common/config/exception/TokenMissingOrExpiredException.java b/src/main/java/com/ai/da/common/config/exception/TokenMissingOrExpiredException.java new file mode 100644 index 00000000..5d47dc41 --- /dev/null +++ b/src/main/java/com/ai/da/common/config/exception/TokenMissingOrExpiredException.java @@ -0,0 +1,12 @@ +package com.ai.da.common.config.exception; + +public class TokenMissingOrExpiredException extends RuntimeException { + public TokenMissingOrExpiredException(String message) { + super(message); + } + + @Override + public Throwable fillInStackTrace() { + return this; + } +} diff --git a/src/main/java/com/ai/da/common/constant/CommonConstant.java b/src/main/java/com/ai/da/common/constant/CommonConstant.java index 71f6c86b..35ff4504 100644 --- a/src/main/java/com/ai/da/common/constant/CommonConstant.java +++ b/src/main/java/com/ai/da/common/constant/CommonConstant.java @@ -75,5 +75,9 @@ public class CommonConstant { public static final String PORTFOLIO_DELETED_CN = "作品已删除"; + public static final String TIME_FORMAT_MMM_dd_yyyy_EEEE = "MMM. dd, yyyy, EEEE"; + public static final String TIME_FORMAT_MMM_dd_yyyy = "MMM. dd, yyyy"; + + public static final String AFFILIATE_LINK = "https://www.aida.com.hk?ref="; } diff --git a/src/main/java/com/ai/da/common/enums/AuthenticationOperationTypeEnum.java b/src/main/java/com/ai/da/common/enums/AuthenticationOperationTypeEnum.java index a8300691..784edb63 100644 --- a/src/main/java/com/ai/da/common/enums/AuthenticationOperationTypeEnum.java +++ b/src/main/java/com/ai/da/common/enums/AuthenticationOperationTypeEnum.java @@ -27,7 +27,11 @@ public enum AuthenticationOperationTypeEnum { /** * 更改邮箱 */ - CHANGE_MAILBOX; + CHANGE_MAILBOX, + /** + * 填写用户国家和职业 + */ + UPDATE_USERINFO; public static AuthenticationOperationTypeEnum of(String name) { return Stream.of(AuthenticationOperationTypeEnum.values()).filter(v -> v.name().equals(name)).findFirst().orElse(null); diff --git a/src/main/java/com/ai/da/common/enums/CreditsEventsEnum.java b/src/main/java/com/ai/da/common/enums/CreditsEventsEnum.java index 532934b0..e8565984 100644 --- a/src/main/java/com/ai/da/common/enums/CreditsEventsEnum.java +++ b/src/main/java/com/ai/da/common/enums/CreditsEventsEnum.java @@ -8,9 +8,11 @@ import lombok.Getter; public enum CreditsEventsEnum { PRICE("price","6"), +// PRICE("price","1"),// for test // PRICE("price","0.1"), BUY_CREDITS("Buy Credits","60"), +// BUY_CREDITS("Buy Credits","10"),// for test REFUND("Refund","60"), // BUY_CREDITS("Buy Credits","10"), @@ -20,6 +22,7 @@ public enum CreditsEventsEnum { INIT_MONTHLY("init_monthly", "5000"), INIT_TRIAL("init_trial", "100"), INIT_WEEKLY("init_weekly","6000"), + RESET_YEAR_CREDITS("reset_year_credits","6000"), // SUPER_RESOLUTION("Super Resolution","30"), SUPER_RESOLUTION("Super Resolution","10"), 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 aac9719d..02767686 100644 --- a/src/main/java/com/ai/da/common/enums/OrderStatusEnum.java +++ b/src/main/java/com/ai/da/common/enums/OrderStatusEnum.java @@ -10,38 +10,34 @@ public enum OrderStatusEnum { * 未支付 */ NOT_PAY("未支付"), - - /** * 支付成功 */ SUCCESS("支付成功"), - + /** + * 支付失败 + */ + FAILURE("支付失败"), /** * 已关闭 */ TIMEOUT_CLOSED("超时已关闭"), - /** * 已取消 */ CANCEL("用户已取消"), - /** * 退款中 */ REFUND_PROCESSING("退款中"), - /** * 已退款 */ REFUND_SUCCESS("已退款"), - /** * 退款异常 */ REFUND_ABNORMAL("退款异常"), - /** * paypal订单状态为 APPROVED */ diff --git a/src/main/java/com/ai/da/common/enums/ProductEnum.java b/src/main/java/com/ai/da/common/enums/ProductEnum.java new file mode 100644 index 00000000..11237ffe --- /dev/null +++ b/src/main/java/com/ai/da/common/enums/ProductEnum.java @@ -0,0 +1,25 @@ +package com.ai.da.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum ProductEnum { + // 积分购买 + CreditsProduct("AiDA credits purchase", 6L), + // 年度订阅 + AnnualSubscription("AiDA Annual Subscription", 5000L), + // 月度订阅 + MonthlySubscription("AiDA Monthly Subscription", 500L), + // 测试 + DailySubscription("AiDA Daily Subscription", 5L), + ; + + /** + * 类型 + */ + private final String name; + + private final Long price; +} 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 a70bc51b..acd4b1be 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 @@ -1,6 +1,7 @@ package com.ai.da.common.security.filter; import cn.hutool.core.util.StrUtil; +import com.ai.da.common.config.exception.TokenMissingOrExpiredException; import com.ai.da.common.context.UserContext; import com.ai.da.common.security.config.SecurityProperties; import com.ai.da.common.security.jwt.JWTTokenHelper; @@ -51,7 +52,7 @@ public class AuthenticationFilter extends OncePerRequestFilter { "/api/python/flush","/api/account/healthy","/api/ali-pay/trade/notify","/api/paypal/ipn/back","/api/alipay-hk/trade/notify", "/api/portfolio/page", "/api/portfolio/detail", "/api/portfolio/commentPage", "/api/portfolio/viewsIncrease", "/api/account/designWorksRegister","/api/account/questionnaire","/api/stripe/trade/notify", - "/notification","/api/account/activateNewEmail","/api/third/party/auth/google_callback" + "/notification","/api/account/activateNewEmail","/api/third/party/auth/google_callback","/api/third/party/parseGoogleCredential","/api/third/party/receiveDesignResults","/api/third/party/parseWeChatCode" ); @Override @@ -101,7 +102,8 @@ public class AuthenticationFilter extends OncePerRequestFilter { if (StrUtil.isBlank(jwtToken)) { String ipAddress = RequestInfoUtil.getIpAddress(request); log.info("本次请求的ip为 : " + ipAddress); - throw new RuntimeException("请传入token!"); +// throw new RuntimeException("请传入token!"); + throw new TokenMissingOrExpiredException("请传入token!"); } if(jwtToken.equals("Bearer-eyJhbGciOiJIUzUxMiJ9.eyJqdGkiOiIyIiwic3ViIjoie1wiaWRcIjoyLFwidXNlcm5hbWVcIjpcImxpcnNcIn0iLCJpYXQiOjE2NjU3NDEwODcsImlzcyI6IkRXSiIsImF1dGhvcml0aWVzIjoiW10iLCJleHAiOjE2NzQzODEwODd9.ShM9R_NNFD7oo1OvxrEgg7PFeWinOuAKkuInUCMQupp66s64Hhv8tN0Wwr83nIN4rHPqtn95wmd4msWcvaFYJA")){ //写死 暂时放行 @@ -112,7 +114,8 @@ public class AuthenticationFilter extends OncePerRequestFilter { if (validate) { AuthPrincipalVo principal = jwtTokenHelper.parserToUser(jwtToken); if (principal == null) { - throw new RuntimeException("TOKEN已过期,请重新登录!"); +// throw new RuntimeException("TOKEN已过期,请重新登录!"); + throw new TokenMissingOrExpiredException("TOKEN已过期,请重新登录!(token without userInfo)"); } //先清空当前线程变量,防止上一个线程遗留 UserContext.delete(); @@ -122,10 +125,12 @@ public class AuthenticationFilter extends OncePerRequestFilter { String cacheToken = LocalCacheUtils.getTokenCache(String.valueOf(principal.getId())); if(StringUtils.isEmpty(cacheToken)){ - throw new RuntimeException("TOKEN已过期,请重新登录!"); +// throw new RuntimeException("TOKEN已过期,请重新登录!"); + throw new TokenMissingOrExpiredException("TOKEN已过期,请重新登录!(local cache empty)"); } if(!cacheToken.equals(jwtToken) ){ - throw new RuntimeException("TOKEN已过期,请重新登录!"); +// throw new RuntimeException("TOKEN已过期,请重新登录!"); + throw new TokenMissingOrExpiredException("TOKEN已过期,请重新登录!(token not match local cache)"); } // UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(null, null); // SecurityContextHolder.getContext().setAuthentication(authentication); diff --git a/src/main/java/com/ai/da/common/task/AccountTask.java b/src/main/java/com/ai/da/common/task/AccountTask.java index 6950878d..b4aacac8 100644 --- a/src/main/java/com/ai/da/common/task/AccountTask.java +++ b/src/main/java/com/ai/da/common/task/AccountTask.java @@ -1,5 +1,6 @@ package com.ai.da.common.task; +import com.ai.da.common.utils.RedisUtil; import com.ai.da.mapper.primary.entity.Account; import com.ai.da.service.AccountService; import lombok.extern.slf4j.Slf4j; @@ -15,25 +16,29 @@ public class AccountTask { @Resource private AccountService accountService; + @Resource + private RedisUtil redisUtil; /** * 每周日晚上刷新 年付用户、月付用户的积分 + * 替换为 + * 每个月月初只刷新年付用户的积分 */ - @Scheduled(cron = "59 59 23 ? * SUN") // @Scheduled(cron = "59 59 23 * * ?") + @Scheduled(cron = "0 0 0 1 * ?") public void refreshCreditsMonthly() { - log.info("每周日晚11:59:59刷新付费用户积分为 6000"); + log.info("每月1号0点 将年费用户积分重置为 6000"); accountService.refreshCreditsWeekly(); } - @Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes +// @Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes public void getPaidUser() { // 获取code-create 表中 指定日期之后 订单状态为wc-processing的订单 accountService.extendValidityForCC(); } // 每天凌晨0点执行一次 - @Scheduled(cron = "0 0 0 * * ?") +// @Scheduled(cron = "0 0 0 * * ?") public void cancelActivityBenefits() { // 1、查询当前所有参与了活动且过期的用户 List accountList = accountService.getExpiredUserBySystemUser(4); @@ -46,7 +51,7 @@ public class AccountTask { } // 每天检测正式用户到期情况,每天凌晨0点执行 - @Scheduled(cron = "0 0 0 * * ?") +// @Scheduled(cron = "0 0 0 * * ?") public void paidUserToVisitor() { // 1、查询当前已过期正式用户或试用用户 List accountList = accountService.getExpiredUserBySystemUser(1); @@ -63,8 +68,15 @@ public class AccountTask { /** * 将Code-Create上注册的用户添加为AiDA的游客 */ - @Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes +// @Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes public void registerUserToVisitor() { accountService.registerUserToVisitor(); } + + @Scheduled(cron = "0 0 0 1 * ?") + // 每月初刷新所有用户用户名剩余修改次数 + public void resetUsernameModifyTimes(){ + log.info("重置所有用户的用户名修改次数"); + redisUtil.batchDeleteKeysWithSamePrefix(RedisUtil.NICKNAME_MODIFY_TIMES); + } } diff --git a/src/main/java/com/ai/da/common/task/AliPayTask.java b/src/main/java/com/ai/da/common/task/AliPayTask.java deleted file mode 100644 index 670a417c..00000000 --- a/src/main/java/com/ai/da/common/task/AliPayTask.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.ai.da.common.task; - -import com.ai.da.mapper.primary.entity.OrderInfo; -import com.ai.da.common.enums.PayTypeEnum; -import com.ai.da.service.AliPayService; -import com.ai.da.service.OrderInfoService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import javax.annotation.Resource; -import java.util.List; - -@Slf4j -@Component -public class AliPayTask { - - @Resource - private OrderInfoService orderInfoService; - - @Resource - private AliPayService aliPayService; - - /** - * 从第0秒开始每隔30秒执行1次,查询创建超过5分钟,并且未支付的订单 - */ -// @Scheduled(cron = "0/30 * * * * ?") - public void orderConfirm(){ - -// log.info("Alipay orderConfirm 被执行......"); - - List orderInfoList = orderInfoService.getNoPayOrderByDuration(5, PayTypeEnum.ALIPAY.getType()); - - for (OrderInfo orderInfo : orderInfoList) { - String orderNo = orderInfo.getOrderNo(); - log.warn("超时订单 ===> {}", orderNo); - - //核实订单状态:调用支付宝查单接口 - aliPayService.checkOrderStatus(orderNo); - } - } -} diff --git a/src/main/java/com/ai/da/common/task/GenerateTask.java b/src/main/java/com/ai/da/common/task/GenerateTask.java deleted file mode 100644 index 038976d0..00000000 --- a/src/main/java/com/ai/da/common/task/GenerateTask.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.ai.da.common.task; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -@Component -@Slf4j -public class GenerateTask { - -// @Scheduled(cron = "0 0 */1 * * ?") - public void generateScheduled(){ - log.info("测试定时器:generate"); - - try{ - - }catch(Exception e){ - - } - } -} diff --git a/src/main/java/com/ai/da/common/task/PaymentTask.java b/src/main/java/com/ai/da/common/task/PaymentTask.java new file mode 100644 index 00000000..f926e6f3 --- /dev/null +++ b/src/main/java/com/ai/da/common/task/PaymentTask.java @@ -0,0 +1,122 @@ +package com.ai.da.common.task; + +import com.ai.da.common.enums.PayTypeEnum; +import com.ai.da.mapper.primary.entity.OrderInfo; +import com.ai.da.service.*; +import com.paypal.http.exceptions.SerializeException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.time.LocalDate; +import java.util.List; + +@Slf4j +@Component +public class PaymentTask { + + @Resource + private OrderInfoService orderInfoService; + + @Resource + private StripeService stripeService; + + @Resource + private AffiliateService affiliateService; + + // 考虑删除该定时任务(原因:之后的订单列允许用户查看发票,发票未过期时仍可以支付,所以不需要手动使订单过期) +// @Scheduled(cron = "0/30 * * * * ?") + public void orderConfirmForStripe() throws SerializeException { + + // 查看超过30分钟以上仍未支付的订单 置为超时订单 + List orderInfoList = orderInfoService.getNoPayOrderByDuration(30, PayTypeEnum.STRIPE.getType()); + + for (OrderInfo orderInfo : orderInfoList) { + String orderNo = orderInfo.getOrderNo(); + log.warn("超时订单 ===> {}", orderNo); + + //核实订单状态:调用支付宝查单接口 + stripeService.checkOrderStatus(orderNo); + + } + } + + @Resource + private PayPalCheckoutService payPalCheckoutService; + + // @Scheduled(cron = "0/30 * * * * ?") + public void orderConfirmForPaypal() throws SerializeException { + +// log.info("PayPal orderConfirm 被执行......"); + + List orderInfoList = orderInfoService.getNoPayOrderByDuration(30, PayTypeEnum.PAYPAL.getType()); + + for (OrderInfo orderInfo : orderInfoList) { + String orderNo = orderInfo.getOrderNo(); + log.warn("超时订单 ===> {}", orderNo); + + //核实订单状态:调用支付宝查单接口 + payPalCheckoutService.checkOrderStatus(orderNo); + + } + } + + @Resource + private AliPayService aliPayService; + + /** + * 从第0秒开始每隔30秒执行1次,查询创建超过5分钟,并且未支付的订单 + */ +// @Scheduled(cron = "0/30 * * * * ?") + public void orderConfirmForAlipay(){ +/* + log.info("Alipay orderConfirm 被执行......"); + + List orderInfoList = orderInfoService.getNoPayOrderByDuration(5, PayTypeEnum.ALIPAY.getType()); + + for (OrderInfo orderInfo : orderInfoList) { + String orderNo = orderInfo.getOrderNo(); + log.warn("超时订单 ===> {}", orderNo); + + //核实订单状态:调用支付宝查单接口 + aliPayService.checkOrderStatus(orderNo); + }*/ + } + + // 提前7天向用户发送提醒邮件,每天早上8点执行 + @Scheduled(cron = "0 0 8 * * ?") + public void subscriptionReminder(){ + stripeService.subscriptionReminder(); + } + + // 每天凌晨检查subscription中有哪些已过期,更新状态 +// @Scheduled(cron = "0 0 0 * * ?") +// public void checkSubscriptionExpiration(){ +// stripeService.checkSubscriptionExpiration(); +// } + + // 如果有订阅已创建,但是没有发邮件通知的,需要主动获取回调信息并向用户发送邮件 + public void checkSubscriptionPayment(){ + // + + } + + @Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes + public void updateAffiliateInfoWithPayment(){ +// log.info("佣金计算定时器"); + affiliateService.updateAffiliateInfoWithPayment(); + } + + @Scheduled(cron = "0 0 8 28-31 * ?") + public void commissionSummaryReminder(){ + // 每个月末的最后一天的早上八点执行 + LocalDate today = LocalDate.now(); + // 判断是否为月底 + if (today.plusDays(1).getDayOfMonth() == 1) { + log.info("今天是月底,执行佣金结算提醒任务!"); + affiliateService.commissionCalculation(null, null); + } + } + +} diff --git a/src/main/java/com/ai/da/common/task/PaypalTask.java b/src/main/java/com/ai/da/common/task/PaypalTask.java deleted file mode 100644 index 865eaaa4..00000000 --- a/src/main/java/com/ai/da/common/task/PaypalTask.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.ai.da.common.task; - -import com.ai.da.common.enums.PayTypeEnum; -import com.ai.da.mapper.primary.entity.OrderInfo; -import com.ai.da.service.OrderInfoService; -import com.ai.da.service.PayPalCheckoutService; -import com.paypal.http.exceptions.SerializeException; -import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import java.util.List; - -@Slf4j -@Component -public class PaypalTask { - - @Resource - private OrderInfoService orderInfoService; - - @Resource - private PayPalCheckoutService payPalCheckoutService; - - @Scheduled(cron = "0/30 * * * * ?") - public void orderConfirm() throws SerializeException { - -// log.info("PayPal orderConfirm 被执行......"); - - List orderInfoList = orderInfoService.getNoPayOrderByDuration(30, PayTypeEnum.PAYPAL.getType()); - - for (OrderInfo orderInfo : orderInfoList) { - String orderNo = orderInfo.getOrderNo(); - log.warn("超时订单 ===> {}", orderNo); - - //核实订单状态:调用支付宝查单接口 - payPalCheckoutService.checkOrderStatus(orderNo); - - } - } -} diff --git a/src/main/java/com/ai/da/common/task/StripeTask.java b/src/main/java/com/ai/da/common/task/StripeTask.java deleted file mode 100644 index 45d98997..00000000 --- a/src/main/java/com/ai/da/common/task/StripeTask.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.ai.da.common.task; - -import com.ai.da.common.enums.PayTypeEnum; -import com.ai.da.mapper.primary.entity.OrderInfo; -import com.ai.da.service.OrderInfoService; -import com.ai.da.service.StripeService; -import com.paypal.http.exceptions.SerializeException; -import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import javax.annotation.Resource; -import java.util.List; - -@Slf4j -@Component -public class StripeTask { - - @Resource - private OrderInfoService orderInfoService; - - @Resource - private StripeService stripeService; - - @Scheduled(cron = "0/30 * * * * ?") - public void orderConfirm() throws SerializeException { - - // 查看超过30分钟以上仍未支付的订单 置为超时订单 - List orderInfoList = orderInfoService.getNoPayOrderByDuration(30, PayTypeEnum.STRIPE.getType()); - - for (OrderInfo orderInfo : orderInfoList) { - String orderNo = orderInfo.getOrderNo(); - log.warn("超时订单 ===> {}", orderNo); - - //核实订单状态:调用支付宝查单接口 - stripeService.checkOrderStatus(orderNo); - - } - } -} diff --git a/src/main/java/com/ai/da/common/utils/DateUtil.java b/src/main/java/com/ai/da/common/utils/DateUtil.java index 94269826..e386ad54 100644 --- a/src/main/java/com/ai/da/common/utils/DateUtil.java +++ b/src/main/java/com/ai/da/common/utils/DateUtil.java @@ -9,8 +9,8 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; -import java.util.Calendar; import java.util.Date; +import java.util.Locale; import java.util.TimeZone; @Slf4j @@ -18,6 +18,7 @@ public class DateUtil { public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; public static final String YYYYMM = "yyyyMM"; public static final String YYYY_MM_DD = "yyyyMMdd"; + public static final String YYYY_MM_DD_hh_mm_ss = "yyyyMMddHHMMss"; /** * LocalDate -> Date @@ -81,4 +82,21 @@ public class DateUtil { return String.valueOf(epochSecond).substring(0, 10); } + public static String changeTimeStampFormat(Long timeStamp, String type, String format){ + // 将秒级时间戳转换为毫秒级 + if (type.equals("seconds")){ + timeStamp = timeStamp * 1000; + } + // 输出格式 + SimpleDateFormat outputFormat = new SimpleDateFormat(format, Locale.ENGLISH); + // 创建Date对象 + Date date = new Date(timeStamp); + // 格式化输出 + return outputFormat.format(date); + } + + public static String changeTimeStampFormat(LocalDateTime localDate){ + return localDate.format(DateTimeFormatter.ofPattern("MMM. dd, yyyy, EEEE", Locale.US)); + } + } 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 7d5295dc..a0d42840 100644 --- a/src/main/java/com/ai/da/common/utils/RedisUtil.java +++ b/src/main/java/com/ai/da/common/utils/RedisUtil.java @@ -278,4 +278,26 @@ public class RedisUtil { // 设置过期时间为 5 分钟(300 秒) redisTemplate.expire(redisKey, 5, TimeUnit.MINUTES); } + + public final static String PAYMENT_INFO_LAST_SCAN_TIME = "PaymentInfoLastScanTime"; + + public final static String AFFILIATE_LINK_VIEW_KEY = "AffiliateLink:view:"; + + public void increaseAffiliateLinkViewCount(Long accountId) { + String key = AFFILIATE_LINK_VIEW_KEY + accountId; + redisTemplate.opsForValue().increment(key); + } + + public Long getAffiliateLinkViewCount(Long accountId) { + String key = AFFILIATE_LINK_VIEW_KEY + accountId; + return redisTemplate.opsForValue().increment(key, 0); + } + + public void batchDeleteKeysWithSamePrefix(String prefix){ + Set keys = redisTemplate.keys(prefix + "*"); + assert keys != null; + if (!keys.isEmpty()){ + redisTemplate.delete(keys); + } + } } diff --git a/src/main/java/com/ai/da/common/utils/RequestInfoUtil.java b/src/main/java/com/ai/da/common/utils/RequestInfoUtil.java index 5f87110e..d041362f 100644 --- a/src/main/java/com/ai/da/common/utils/RequestInfoUtil.java +++ b/src/main/java/com/ai/da/common/utils/RequestInfoUtil.java @@ -1,7 +1,16 @@ package com.ai.da.common.utils; -import javax.servlet.http.HttpServletRequest; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import javax.servlet.http.HttpServletRequest; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Map; + +@Slf4j public class RequestInfoUtil { /** @@ -45,4 +54,58 @@ public class RequestInfoUtil { return ip; } + /** + * 免费 API 服务可能有请求频率限制,如果你需要处理大量 IP 地址,可能需要考虑使用付费服务或购买 IP 地理位置数据库。此外,始终要遵守 API 提供商的使用条款和隐私政策 + * @param ip https://ip-api.com/docs/api:json 使用的接口api + * @return + * { + * "query": "24.48.0.1", + * "status": "success", + * "country": "Canada", + * "countryCode": "CA", + * "region": "QC", + * "regionName": "Quebec", + * "city": "Montreal", + * "zip": "H1L", + * "lat": 45.6026, + * "lon": -73.5167, + * "timezone": "America/Toronto", + * "isp": "Le Groupe Videotron Ltee", + * "org": "Videotron Ltee", + * "as": "AS5769 Videotron Ltee" + * } + */ + public static Map getIPLocation(String ip) { +// String ip = "117.143.125.1"; // 替换为你想查询的 IP 地址 +// String ip = "194.5.48.180"; // 替换为你想查询的 IP 地址 + String apiURL = "http://ip-api.com/json/" + ip; + + try { + URL url = new URL(apiURL); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setRequestProperty("Accept", "application/json"); + + if (conn.getResponseCode() != 200) { + throw new RuntimeException("Failed : HTTP error code : " + conn.getResponseCode()); + } + + BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String output; + StringBuilder outputBuilder = new StringBuilder(); + System.out.println("Output from Server .... \n"); + while ((output = br.readLine()) != null) { + outputBuilder.append(output); + System.out.println(output); + } + conn.disconnect(); + Map map = JSONObject.parseObject(outputBuilder.toString(), Map.class); + log.info("map: {}", map); + return map; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + } diff --git a/src/main/java/com/ai/da/common/utils/SendEmailUtil.java b/src/main/java/com/ai/da/common/utils/SendEmailUtil.java index 25e07339..7770019e 100644 --- a/src/main/java/com/ai/da/common/utils/SendEmailUtil.java +++ b/src/main/java/com/ai/da/common/utils/SendEmailUtil.java @@ -2,8 +2,11 @@ package com.ai.da.common.utils; import com.ai.da.mapper.primary.entity.Account; import com.ai.da.mapper.primary.entity.TrialOrder; +import com.ai.da.model.dto.AffiliateEmailParamsDTO; +import com.ai.da.model.dto.SubscriptionEmailParamsDTO; import com.alibaba.fastjson.JSONObject; import com.ai.da.common.config.exception.BusinessException; +import com.alibaba.fastjson2.JSON; import com.tencentcloudapi.common.Credential; import com.tencentcloudapi.common.exception.TencentCloudSDKException; import com.tencentcloudapi.common.profile.ClientProfile; @@ -70,7 +73,8 @@ public class SendEmailUtil { /** * 绑定邮箱模板id */ - public static Long BIND_MAILBOX_TEMPLATE_ID = 45619L; +// public static Long BIND_MAILBOX_TEMPLATE_ID = 45619L; + public static Long BIND_MAILBOX_TEMPLATE_ID = 132754L; public static Long CHANGE_MAILBOX_TEMPLATE_ID = 128210L; @@ -175,7 +179,7 @@ public class SendEmailUtil { subject = "Approval Confirmation for AiDA System Trial Access"; if (country.equals("China")) { template.setTemplateID(NOTIFICATION_CHINESE_TEMPLATE_ID); - }else { + } else { template.setTemplateID(NOTIFICATION_TEMPLATE_ID); } template.setTemplateData(buildNotificationData(trialOrder, link)); @@ -225,7 +229,7 @@ public class SendEmailUtil { attachment.setFileName(fileName); // 设置附件文件名 // 设置附件内容 attachment.setContent(Base64.getEncoder().encodeToString(fileContent)); - req.setAttachments(new Attachment[] {attachment}); + req.setAttachments(new Attachment[]{attachment}); // 发送邮件 SendEmailResponse resp = client.SendEmail(req); log.info("短信发送结果res###{}", SendEmailResponse.toJsonString(resp)); @@ -268,7 +272,9 @@ public class SendEmailUtil { throw new BusinessException("failed.to.send.mail"); } } + private final static Long WILLBEEXPIRED_TEMPLATE_ID = 118178L; + public static void sendWillBeExpiredEmail(Account account, String senderAddress) { try { // 实例化一个认证对象 @@ -360,7 +366,7 @@ public class SendEmailUtil { jsonObject.put("email", trialOrder.getEmail()); if (link) { jsonObject.put("days", 14); - }else { + } else { jsonObject.put("days", 5); } return jsonObject.toJSONString(); @@ -370,6 +376,7 @@ public class SendEmailUtil { private final static Long UPGRADE_SUCCESS_NOTIFICATION_ID = 118856L; private final static Long UPGRADE_NOTIFICATION_ID_CHINESE = 122898L; private final static Long UPGRADE_SUCCESS_NOTIFICATION_ID_CHINESE = 122899L; + public static void sendUpgradeNotification(Account account, String senderAddress, Integer type) { try { // 实例化一个认证对象 @@ -418,7 +425,8 @@ public class SendEmailUtil { } private final static Long GENERATE_EXCEPTION_WARNING_ID = 122589L; - public static void sendGenerateExceptionWarning(String message){ + + public static void sendGenerateExceptionWarning(String message) { try { // 实例化一个认证对象 Credential cred = new Credential(SECRET_ID, SECRET_KEy); @@ -457,7 +465,8 @@ public class SendEmailUtil { private final static Long QUESTIONNAIRE_FEEDBACK_EN_ID = 124151L; private final static Long QUESTIONNAIRE_FEEDBACK_CN_ID = 124156L; - public static void questionnaireRelatedNotify(String userName, String email, String language){ + + public static void questionnaireRelatedNotify(String userName, String email, String language) { try { // 实例化一个认证对象 Credential cred = new Credential(SECRET_ID, SECRET_KEy); @@ -502,7 +511,7 @@ public class SendEmailUtil { private final static Long RENEWAL_NOTIFICATION_FOR_OLD_USER_EN = 124892L; private final static Long RENEWAL_NOTIFICATION_FOR_OLD_USER_CN = 124891L; - public static void notificationForPaidUser(String receiverAddress, int emailType, String country, String userName, String date){ + public static void notificationForPaidUser(String receiverAddress, int emailType, String country, String userName, String date) { try { // 实例化一个认证对象 Credential cred = new Credential(SECRET_ID, SECRET_KEy); @@ -525,7 +534,7 @@ public class SendEmailUtil { subject = "Welcome to AiDA!"; if (country.equals("China")) { template.setTemplateID(NEW_USER_PAYMENT_NOTIFICATION_CN); - }else { + } else { template.setTemplateID(NEW_USER_PAYMENT_NOTIFICATION_EN); } parameter.put("userName", userName); @@ -536,7 +545,7 @@ public class SendEmailUtil { subject = "Account renewal notification"; if (country.equals("China")) { template.setTemplateID(RENEWAL_NOTIFICATION_FOR_OLD_USER_CN); - }else { + } else { template.setTemplateID(RENEWAL_NOTIFICATION_FOR_OLD_USER_EN); } break; @@ -597,8 +606,8 @@ public class SendEmailUtil { private final static Long NEW_USER_REGISTER_NOTIFICATION_EN = 126919L; - public static void notificationForRegisterUser(String receiverAddress){ - try{ + public static void notificationForRegisterUser(String receiverAddress) { + try { // 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密 // 代码泄露可能会导致 SecretId 和 SecretKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议采用更安全的方式来使用密钥,请参见:https://cloud.tencent.com/document/product/1278/85305 // 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取 @@ -634,8 +643,8 @@ public class SendEmailUtil { private final static Long CHANGE_MAILBOX_CONFIRM_CN = 128278L; private final static Long CHANGE_MAILBOX_CONFIRM_EN = 128277L; - public static void changeMailboxConfirm(String receiverAddress, String language, String name, String link){ - try{ + public static void changeMailboxConfirm(String receiverAddress, String language, String name, String link) { + try { // 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密 // 代码泄露可能会导致 SecretId 和 SecretKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议采用更安全的方式来使用密钥,请参见:https://cloud.tencent.com/document/product/1278/85305 // 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取 @@ -653,10 +662,10 @@ public class SendEmailUtil { req.setFromEmailAddress(SEND_ADDRESS); req.setDestination(new String[]{receiverAddress}); Template template = new Template(); - if (language.equals("ENGLISH")){ + if (language.equals("ENGLISH")) { req.setSubject("Change the email address bound to the AiDA account"); template.setTemplateID(CHANGE_MAILBOX_CONFIRM_EN); - }else { + } else { req.setSubject("更换AiDA账号绑定的邮箱地址"); template.setTemplateID(CHANGE_MAILBOX_CONFIRM_CN); } @@ -678,12 +687,12 @@ public class SendEmailUtil { private final static Long UPLOAD_TIMEOUT_REMINDER = 128324L; - public static void uploadTimeoutReminder(String userName, String time){ + public static void uploadTimeoutReminder(String userName, String time) { String xp = "xupei3360@163.com"; String shb = "shahaibodd99@gmail.com"; String wxd = "X1627315083@163.com"; String pkc = "kaicpang.pang@connect.polyu.hk"; - try{ + try { // 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密 // 代码泄露可能会导致 SecretId 和 SecretKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议采用更安全的方式来使用密钥,请参见:https://cloud.tencent.com/document/product/1278/85305 // 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取 @@ -721,6 +730,7 @@ public class SendEmailUtil { private final static Long HALFPRICEPROMOTION_CN_ID = 128582L; private final static Long HALFPRICEPROMOTION_EN_ID = 128583L; + public static void halfPricePromotion(Account account, String senderAddress, int type) { try { // 实例化一个认证对象 @@ -750,7 +760,7 @@ public class SendEmailUtil { if (type == 1) { subject = "AiDA workshop - Win a trip to Hong Kong!"; template.setTemplateID(HALFPRICEPROMOTION_EN_ID); - }else { + } else { subject = "AiDA workshop - 赢取香港之旅"; template.setTemplateID(HALFPRICEPROMOTION_CN_ID); } @@ -808,4 +818,212 @@ public class SendEmailUtil { throw new BusinessException("failed.to.send.mail"); } } + + private final static Long CANCEL_MERCHANT_EN = 130720L; + private final static Long NEW_MERCHANT_EN = 130721L; + private final static Long NEW_USER_EN = 130722L; + private final static Long NEW_USER_CN = 130723L; + private final static Long RENEWAL_MERCHANT_EN = 130724L; + private final static Long RENEWAL_USER_EN = 130725L; + private final static Long RENEWAL_USER_CN = 130726L; + private final static Long RENEWAL_REMINDER_USER_EN = 130727L; + private final static Long RENEWAL_REMINDER_USER_CN = 130728L; + private final static Long PAYMENT_FAILED_NEW_MERCHANT_EN = 131230L; + private final static Long PAYMENT_FAILED_RENEWAL_MERCHANT_EN = 131225L; + private final static Long PAYMENT_FAILED_RENEWAL_USER_EN = 131563L; + private final static Long PAYMENT_FAILED_RENEWAL_USER_CN = 131564L; + + public static void subscriptionEmailReminder(String type, SubscriptionEmailParamsDTO subscriptionEmailParamsDTO, String language, String receiverAddress) { + try { +// String merchantEmail = "kimwong@code-create.com.hk"; + String developer = "xupei3360@163.com"; + String[] receiverEmail = {/*merchantEmail, */developer}; + Credential cred = new Credential(SECRET_ID, SECRET_KEy); + // 实例化一个http选项,可选的,没有特殊需求可以跳过 + HttpProfile httpProfile = new HttpProfile(); + httpProfile.setEndpoint("ses.tencentcloudapi.com"); + // 实例化一个client选项,可选的,没有特殊需求可以跳过 + ClientProfile clientProfile = new ClientProfile(); + clientProfile.setHttpProfile(httpProfile); + // 实例化要请求产品的client对象,clientProfile是可选的 + SesClient client = new SesClient(cred, "ap-hongkong", clientProfile); + // 实例化一个请求对象,每个接口都会对应一个request对象 + SendEmailRequest user = new SendEmailRequest(); + user.setFromEmailAddress(SEND_ADDRESS); + user.setDestination(new String[]{receiverAddress}); + SendEmailRequest merchant = new SendEmailRequest(); + merchant.setFromEmailAddress(SEND_ADDRESS); + merchant.setDestination(receiverEmail); + Template templateUser = new Template(); + Template templateMerchant = new Template(); + switch (type) { + case "cancel": + merchant.setSubject("[Code-Create] Subscription Cancelled"); + templateMerchant.setTemplateID(CANCEL_MERCHANT_EN); + break; + case "fail_new": + merchant.setSubject("[Code-Create] Payment Failed : New Order (" + subscriptionEmailParamsDTO.getOrderId() + ")"); + templateMerchant.setTemplateID(PAYMENT_FAILED_NEW_MERCHANT_EN); + break; + case "fail_renewal": + merchant.setSubject("[Code-Create] Payment Failed : Renewal Order (" + subscriptionEmailParamsDTO.getOrderId() + ")"); + templateMerchant.setTemplateID(PAYMENT_FAILED_RENEWAL_MERCHANT_EN); + // todo to user + if (language.equals("ENGLISH")) { + user.setSubject("[Code-Create] Payment Failed : Renewal Order (" + subscriptionEmailParamsDTO.getOrderId() + ")"); + templateUser.setTemplateID(PAYMENT_FAILED_RENEWAL_USER_EN); + } else { + user.setSubject("[Code-Create] 自动续费失败 (" + subscriptionEmailParamsDTO.getOrderId() + ")"); + templateUser.setTemplateID(PAYMENT_FAILED_RENEWAL_USER_CN); + } + break; + case "new": + merchant.setSubject("[Code-Create] New Order(" + subscriptionEmailParamsDTO.getOrderId() + ")"); + templateMerchant.setTemplateID(NEW_MERCHANT_EN); + if (language.equals("ENGLISH")) { + user.setSubject("[Code-Create] You have successfully subscribed to AiDA"); + templateUser.setTemplateID(NEW_USER_EN); + } else { + user.setSubject("[Code-Create] 您已成功订阅AiDA"); + templateUser.setTemplateID(NEW_USER_CN); + } + break; + case "renewal": + merchant.setSubject("[Code-Create] New subscription renewal order (" + subscriptionEmailParamsDTO.getOrderId() + ")"); + templateMerchant.setTemplateID(RENEWAL_MERCHANT_EN); + if (language.equals("ENGLISH")) { + user.setSubject("[Code-Create] AiDA Renewal Successful"); + templateUser.setTemplateID(RENEWAL_USER_EN); + } else { + user.setSubject("[Code-Create] AiDA续订成功"); + templateUser.setTemplateID(RENEWAL_USER_CN); + } + break; + case "reminder": + if (language.equals("ENGLISH")) { + user.setSubject("[Code-Create] AiDA Subscription Renewal Reminder"); + templateUser.setTemplateID(RENEWAL_REMINDER_USER_EN); + } else { + user.setSubject("[Code-Create] AiDA续订提醒"); + templateUser.setTemplateID(RENEWAL_REMINDER_USER_CN); + } + break; + default: + log.error("unknown subscription email type"); +// throw new BusinessException("unknown subscription email type"); + } + + templateUser.setTemplateData(JSON.toJSONString(subscriptionEmailParamsDTO)); + user.setTemplate(templateUser); + + templateMerchant.setTemplateData(JSON.toJSONString(subscriptionEmailParamsDTO)); + merchant.setTemplate(templateMerchant); + + if (!type.equals("cancel") && !type.equals("fail_new")) { + // 返回的resp是一个SendEmailResponse的实例,与请求对象对应 + SendEmailResponse respUser = client.SendEmail(user); + log.info("邮件主题:{},发送结果toUser###{}", user.getSubject(), SendEmailResponse.toJsonString(respUser)); + } + if (!type.equals("reminder")) { + SendEmailResponse respMerchant = client.SendEmail(merchant); + log.info("邮件主题:{},发送结果toMerchant###{}", merchant.getSubject(), SendEmailResponse.toJsonString(respMerchant)); + } + } catch (TencentCloudSDKException e) { + log.info("邮件发送失败###{}", e.toString()); + throw new BusinessException("failed.to.send.mail"); + } + } + + private final static Long NEW_REGISTRATION = 132123L; + private final static Long AFFILIATE_ACCEPTED = 132124L; + private final static Long AFFILIATE_REFUSED = 132125L; + private final static Long AFFILIATE_MONTHLY_SUMMARY = 132126L; + + public static void affiliateEmailReminder(String[] receiverAddress, AffiliateEmailParamsDTO paramsDTO, String type) { + try { + Credential cred = new Credential(SECRET_ID, SECRET_KEy); + // 实例化一个http选项,可选的,没有特殊需求可以跳过 + HttpProfile httpProfile = new HttpProfile(); + httpProfile.setEndpoint("ses.tencentcloudapi.com"); + // 实例化一个client选项,可选的,没有特殊需求可以跳过 + ClientProfile clientProfile = new ClientProfile(); + clientProfile.setHttpProfile(httpProfile); + // 实例化要请求产品的client对象,clientProfile是可选的 + SesClient client = new SesClient(cred, "ap-hongkong", clientProfile); + // 实例化一个请求对象,每个接口都会对应一个request对象 + SendEmailRequest req = new SendEmailRequest(); + req.setFromEmailAddress(SEND_ADDRESS); + req.setDestination(receiverAddress); + Template template = new Template(); + switch (type) { + case "new": + req.setSubject("New Affiliate Registration"); + template.setTemplateID(NEW_REGISTRATION); + break; + case "accepted": + req.setSubject("Affiliate Application Accepted"); + template.setTemplateID(AFFILIATE_ACCEPTED); + break; + case "refused": + req.setSubject("Affiliate Application Refused"); + template.setTemplateID(AFFILIATE_REFUSED); + break; + case "summary": + req.setSubject("Your Monthly AffiliateWP Summary for AiDA"); + template.setTemplateID(AFFILIATE_MONTHLY_SUMMARY); + break; + } + + template.setTemplateData(JSON.toJSONString(paramsDTO)); + req.setTemplate(template); + + // 返回的resp是一个SendEmailResponse的实例,与请求对象对应 + SendEmailResponse resp = client.SendEmail(req); + log.info("短信发送结果res###{}", SendEmailResponse.toJsonString(resp)); + } catch (TencentCloudSDKException e) { + log.info("邮件发送失败###{}", e.toString()); + throw new BusinessException("failed.to.send.mail"); + } + } + + private final static Long CREDITS_PURCHASE_MERCHANT = 133275L; + + public static void creditsPurchaseReminder(String username, String quantity, String amount) { + try { + Credential cred = new Credential(SECRET_ID, SECRET_KEy); + // 实例化一个http选项,可选的,没有特殊需求可以跳过 + HttpProfile httpProfile = new HttpProfile(); + httpProfile.setEndpoint("ses.tencentcloudapi.com"); + // 实例化一个client选项,可选的,没有特殊需求可以跳过 + ClientProfile clientProfile = new ClientProfile(); + clientProfile.setHttpProfile(httpProfile); + // 实例化要请求产品的client对象,clientProfile是可选的 + SesClient client = new SesClient(cred, "ap-hongkong", clientProfile); + // 实例化一个请求对象,每个接口都会对应一个request对象 + SendEmailRequest req = new SendEmailRequest(); + req.setFromEmailAddress(SEND_ADDRESS); + String merchantEmail = "kimwong@code-create.com.hk"; + String developerEmail = "xupei@code-create.com.hk"; + req.setDestination(new String[]{merchantEmail, developerEmail}); + Template template = new Template(); + req.setSubject("New Credit Purchase Order"); + template.setTemplateID(CREDITS_PURCHASE_MERCHANT); + JSONObject jsonObject = new JSONObject(); + // 设置试用订单相关数据 + jsonObject.put("userName", username); + jsonObject.put("quantity", quantity); + jsonObject.put("totalFee", amount); + + template.setTemplateData(JSON.toJSONString(jsonObject)); + req.setTemplate(template); + + // 返回的resp是一个SendEmailResponse的实例,与请求对象对应 + SendEmailResponse resp = client.SendEmail(req); + log.info("短信发送结果res###{}", SendEmailResponse.toJsonString(resp)); + } catch (TencentCloudSDKException e) { + log.info("邮件发送失败###{}", e.toString()); + throw new BusinessException("failed.to.send.mail"); + } + } + } diff --git a/src/main/java/com/ai/da/controller/AccountController.java b/src/main/java/com/ai/da/controller/AccountController.java index ebf9b8a8..de8dc770 100644 --- a/src/main/java/com/ai/da/controller/AccountController.java +++ b/src/main/java/com/ai/da/controller/AccountController.java @@ -3,10 +3,13 @@ package com.ai.da.controller; import com.ai.da.common.config.exception.BusinessException; import com.ai.da.common.response.PageBaseResponse; import com.ai.da.common.response.Response; +import com.ai.da.mapper.primary.entity.Account; +import com.ai.da.mapper.primary.entity.AccountExtend; import com.ai.da.mapper.primary.entity.TrialOrder; import com.ai.da.model.dto.*; import com.ai.da.model.vo.AccountLoginVO; import com.ai.da.model.vo.AccountPreLoginVO; +import com.ai.da.model.vo.BindEmailVO; import com.ai.da.model.vo.PersonalHomepageVO; import com.ai.da.service.AccountService; import io.swagger.annotations.Api; @@ -48,8 +51,8 @@ public class AccountController { @ApiOperation(value = "绑定邮箱") @PostMapping("/bindEmail") - public Response bindEmail(@Valid @RequestBody AccountBindEmailDTO accountBindEmailDTO) { - return Response.success(accountService.bindEmail(accountBindEmailDTO)); + public Response bindEmail(@Valid @RequestBody AccountBindEmailDTO accountBindEmailDTO, HttpServletRequest request) { + return Response.success(accountService.bindEmail(accountBindEmailDTO, request)); } @ApiOperation(value = "忘记密码") @@ -222,7 +225,7 @@ public class AccountController { @ApiOperation(value = "getUsernameModifyTimes") @GetMapping("/getNicknameModifyTimes") - public Response> getNicknameModifyTimes(){ + public Response getNicknameModifyTimes(){ return Response.success(accountService.getNicknameModifyTimes()); } @@ -233,7 +236,7 @@ public class AccountController { return Response.success("success"); } - @ApiOperation(value = "verifyUserEmail") + /*@ApiOperation(value = "verifyUserEmail") @GetMapping("/verifyUserEmail") public Response verifyUserEmail(@RequestParam("verifyCode") String verifyCode){ accountService.verifyUserEmail(verifyCode); @@ -252,7 +255,7 @@ public class AccountController { public Response activateNewEmail(@RequestParam("token") String token){ accountService.activateNewEmail(token); return Response.success("success"); - } + }*/ @PostMapping("halfPricePromotion") @ApiOperation(value = "十月半价活动") @@ -268,5 +271,82 @@ public class AccountController { return Response.success(true); } + @PostMapping("enterpriseLogin") + @ApiOperation(value = "企业登录") + public Response enterpriseLogin(@Valid @RequestBody AccountLoginDTO accountDTO) { + return Response.success(accountService.enterpriseLogin(accountDTO)); + } + @PostMapping("schoolLogin") + @ApiOperation(value = "学校登录") + public Response schoolLogin(@Valid @RequestBody AccountLoginDTO accountDTO) { + return Response.success(accountService.schoolLogin(accountDTO)); + } + + @PostMapping("addOrUpdateSubAccount") + @ApiOperation(value = "子账号新增") + public Response addSubAccount(@Valid @RequestBody AddSubAccountDTO addSubAccountDTO) { + return Response.success(accountService.addSubAccount(addSubAccountDTO)); + } + + @PostMapping("deleteSubAccount") + @ApiOperation(value = "子账号删除") + public Response deleteSubAccount(@Valid @RequestBody AddSubAccountDTO addSubAccountDTO) { + return Response.success(accountService.deleteSubAccount(addSubAccountDTO)); + } + + @PostMapping("subAccountList") + @ApiOperation(value = "子账号查询") + public Response> subAccountList(@Valid @RequestBody SubAccountPageDTO subAccountPageDTO) { + return Response.success(accountService.subAccountList(subAccountPageDTO)); + } + + @GetMapping("accountDetail") + @ApiOperation(value = "账号详情") + public Response accountDetail(@RequestParam("id") Long id) { + return Response.success(accountService.accountDetail(id)); + } + + @PostMapping("getAccountDetail") + @ApiOperation(value = "获取账户信息") + public Response getAccountDetail() { + return Response.success(accountService.getAccountDetail()); + } + + @GetMapping("/bindGoogle") + @ApiOperation(value = "绑定谷歌") + public Response bindGoogle(@RequestParam("credential") String credential) { + return Response.success(accountService.bindGoogle(credential)); + } + + @GetMapping("/bindWeChat") + @ApiOperation(value = "绑定微信") + public Response bindWeChat(@RequestParam("code") String code) { + return Response.success(accountService.bindWeChat(code)); + } + + @GetMapping("/bindEmail") + @ApiOperation(value = "绑定邮箱") + public Response bindEmail(@RequestParam("email") String email) { + return Response.success(accountService.bindEmail(email)); + } + + @GetMapping("/unbindWeChat") + @ApiOperation(value = "解除绑定微信") + public Response unbindWeChat() { + return Response.success(accountService.unbindWeChat()); + } + + @GetMapping("/unbindGoogle") + @ApiOperation(value = "解除绑定谷歌") + public Response unbindGoogle() { + return Response.success(accountService.unbindGoogle()); + } + + @GetMapping("/updateUserInfo") + @ApiOperation(value = "更新用户国家、职业信息") + public Response updateUserInfo(@RequestParam(value = "country", required = false) String country, @RequestParam(value = "occupation", required = false) String occupation) { + accountService.updateUserInfo(country, occupation); + return Response.success(); + } } diff --git a/src/main/java/com/ai/da/controller/AffiliateController.java b/src/main/java/com/ai/da/controller/AffiliateController.java new file mode 100644 index 00000000..004bc6e7 --- /dev/null +++ b/src/main/java/com/ai/da/controller/AffiliateController.java @@ -0,0 +1,85 @@ +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.AffiliateQueryDTO; +import com.ai.da.model.vo.AffiliateInvitationDetailsVO; +import com.ai.da.model.vo.AffiliateVO; +import com.ai.da.service.AffiliateService; +import com.baomidou.mybatisplus.core.metadata.IPage; +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; + +@Slf4j +@RestController +@RequestMapping("/api/affiliate") +@Api(tags = "Affiliate模块") +public class AffiliateController { + + @Resource + private AffiliateService affiliateService; + + @ApiOperation(value = "注册成为affiliate") + @GetMapping("/registration") + public Response completeGuidance(@RequestParam(value = "promotionMethod", required = false) String promotionMethod) { + return Response.success(affiliateService.registerAsAnAffiliate(promotionMethod)); + } + + @ApiOperation(value = "获取affiliate列表") + @PostMapping("/list") + public Response> getAffiliateList(@Valid @RequestBody AffiliateQueryDTO affiliateQueryDTO) { + return Response.success(affiliateService.getAffiliateList(affiliateQueryDTO)); + } + + @ApiOperation(value = "获取affiliate个人中心") + @GetMapping("/personalCenter") + public Response personalAffiliateCenter() { + return Response.success(affiliateService.personalAffiliateCenter()); + } + + @ApiOperation(value = "获取个人佣金图表数据") + @GetMapping("/getPersonalMonthlyIncome") + public Response getPersonalMonthlyIncome(@RequestParam("year")int year) { + return Response.success(affiliateService.getPersonalMonthlyIncome(year)); + } + + @ApiOperation(value = "审批affiliate申请") + @GetMapping("/approval") + public Response applicationApproval(@RequestParam("id") Long id, @RequestParam("isApproved")Boolean isApproved) { + return Response.success(affiliateService.applicationApproval(id, isApproved)); + } + + /*@ApiOperation(value = "定时计算佣金") + @GetMapping("/testTask") + public Response testTask() { + affiliateService.updateAffiliateInfoWithPayment(); + return Response.success("success "); + }*/ + + /*@ApiOperation(value = "每月发送结算邮件") + @GetMapping("/commissionCalculation") + public Response commissionCalculation(@RequestParam("year") Integer year, @RequestParam("month") Integer month) { + affiliateService.commissionCalculation(year, month); + return Response.success("success "); + }*/ + + @ApiOperation(value = "affiliate链接浏览量增加") + @GetMapping("/viewsIncrease") + public Response viewsGet(@RequestParam("id") Long id) { + return Response.success(affiliateService.affiliateLinkViewsIncrease(id)); + } + + @ApiOperation(value = "获取每个affiliate产生的收入") + @PostMapping("/getEachAffiliateGeneratedRevenue") + public Response> getEachAffiliateGeneratedRevenue(@RequestBody AffiliateQueryDTO affiliateQueryDTO) { + return Response.success(affiliateService.getEachAffiliateGeneratedRevenue(affiliateQueryDTO)); + } + + +} diff --git a/src/main/java/com/ai/da/controller/AliPayController.java b/src/main/java/com/ai/da/controller/AliPayController.java index b3a9c65b..abde0710 100644 --- a/src/main/java/com/ai/da/controller/AliPayController.java +++ b/src/main/java/com/ai/da/controller/AliPayController.java @@ -1,6 +1,7 @@ package com.ai.da.controller; import com.ai.da.common.response.Response; +import com.ai.da.model.dto.ProductPurchaseDTO; import com.ai.da.service.AliPayService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -8,6 +9,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; import java.util.Map; @CrossOrigin @@ -21,12 +24,12 @@ public class AliPayController { private AliPayService aliPayService; @ApiOperation("统一收单下单并支付页面接口的调用") - @PostMapping("/trade/page/pay/{amount}") - public Response tradePagePay(@PathVariable Integer amount, @RequestParam String returnUrl){ + @PostMapping("/trade/page/pay") + public Response tradePagePay(@Valid @RequestBody ProductPurchaseDTO productPurchaseDTO, HttpServletRequest request){ log.info("统一收单下单并支付页面接口的调用"); //支付宝开放平台接受 request 请求对象后 // 会为开发者生成一个html 形式的 form表单,包含自动提交的脚本 - String formStr = aliPayService.tradeCreate(amount, returnUrl); + String formStr = aliPayService.tradeCreate(productPurchaseDTO, request); //我们将form表单字符串返回给前端程序,之后前端将会调用自动提交脚本,进行表单的提交 //此时,表单会自动提交到action属性所指向的支付宝开放平台中,从而为用户展示一个支付页面 return Response.success(formStr); diff --git a/src/main/java/com/ai/da/controller/AlipayHKController.java b/src/main/java/com/ai/da/controller/AlipayHKController.java index 006c00b9..1cfdcf06 100644 --- a/src/main/java/com/ai/da/controller/AlipayHKController.java +++ b/src/main/java/com/ai/da/controller/AlipayHKController.java @@ -1,6 +1,7 @@ package com.ai.da.controller; import com.ai.da.common.response.Response; +import com.ai.da.model.dto.ProductPurchaseDTO; import com.ai.da.service.AlipayHKService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -8,6 +9,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; @CrossOrigin @RestController @@ -20,9 +23,9 @@ public class AlipayHKController { private AlipayHKService alipayHKService; @ApiOperation(value = "创建订单") - @PostMapping(value = "/createOrder/{wallet}/{amount}") - public Response createOrder(@PathVariable Integer amount, @PathVariable String wallet) { - String order = alipayHKService.createOrder(amount, wallet); + @PostMapping(value = "/createOrder") + public Response createOrder(@Valid @RequestBody ProductPurchaseDTO productPurchaseDTO, HttpServletRequest request) { + String order = alipayHKService.createOrder(productPurchaseDTO, request); return Response.success(order); } diff --git a/src/main/java/com/ai/da/controller/ConvenientInquiryController.java b/src/main/java/com/ai/da/controller/ConvenientInquiryController.java index 9795ca1a..78c9ff51 100644 --- a/src/main/java/com/ai/da/controller/ConvenientInquiryController.java +++ b/src/main/java/com/ai/da/controller/ConvenientInquiryController.java @@ -2,20 +2,21 @@ 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.DesignMapper; import com.ai.da.mapper.primary.entity.Account; import com.ai.da.mapper.primary.entity.TrialOrder; import com.ai.da.model.dto.AccountAddDTO; +import com.ai.da.model.dto.QueryPaymentInfoDTO; import com.ai.da.model.dto.UserDesignStatisticDTO; +import com.ai.da.model.vo.PaymentInfoVO; import com.ai.da.model.vo.QuestionnaireFeedbackVO; import com.ai.da.model.vo.QuestionnaireVO; import com.ai.da.model.vo.QueryUserConditionsVO; import com.ai.da.service.AccountService; import com.ai.da.service.ConvenientInquiryService; import com.baomidou.mybatisplus.core.metadata.IPage; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.itextpdf.text.pdf.PRIndirectReference; import io.netty.util.internal.StringUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -25,6 +26,7 @@ import org.springframework.web.bind.annotation.*; import javax.annotation.Nullable; import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import java.text.SimpleDateFormat; import java.util.*; @@ -196,5 +198,21 @@ public class ConvenientInquiryController { return Response.success(convenientInquiryService.getAllUserIdList()); } + @ApiOperation("获取所有交易信息") + @PostMapping("/queryTransaction") + public Response> queryTransactionRecords(@Valid @RequestBody QueryPaymentInfoDTO queryPaymentInfoDTO){ + return Response.success(convenientInquiryService.queryTransactionRecords(queryPaymentInfoDTO)); + } + @ApiOperation("获取所有国家、城市") + @GetMapping("/getCities") + public Response>> getCities(){ + return Response.success(convenientInquiryService.getCities()); + } + + @ApiOperation("下载交易记录") + @PostMapping("/queryTransaction/download") + public Response exportTransactionRecords(@Valid @RequestBody QueryPaymentInfoDTO queryPaymentInfoDTO, HttpServletResponse response){ + return Response.success(convenientInquiryService.exportTransactionRecords(queryPaymentInfoDTO, response)); + } } diff --git a/src/main/java/com/ai/da/controller/DesignController.java b/src/main/java/com/ai/da/controller/DesignController.java index 2ea463c1..6046ed32 100644 --- a/src/main/java/com/ai/da/controller/DesignController.java +++ b/src/main/java/com/ai/da/controller/DesignController.java @@ -31,7 +31,7 @@ public class DesignController { @ApiOperation(value = "设计 Conllection") @PostMapping("/designCollection") @CrossOrigin - public Response designCollection(@Valid @RequestBody DesignCollectionDTO designDTO) { + public Response designCollection(@Valid @RequestBody DesignCollectionDTO designDTO) { return Response.success(designService.designCollection(designDTO)); } @@ -43,7 +43,7 @@ public class DesignController { @ApiOperation(value = "重新设计 Collection") @PostMapping("/reDesignCollection") - public Response reDesignCollection(@Valid @RequestBody ReDesignCollectionDTO reDesignDTO) { + public Response reDesignCollection(@Valid @RequestBody ReDesignCollectionDTO reDesignDTO) { return Response.success(designService.reDesignCollection(reDesignDTO)); } @@ -71,6 +71,12 @@ public class DesignController { return Response.success(designService.dislike(disDesignLikeDTO)); } + @ApiOperation(value = "Design sort") + @PostMapping("/sort") + public Response sort(@Valid @RequestBody UserLikeSortDTO userLikeSortDTO) { + return Response.success(designService.sort(userLikeSortDTO)); + } + @ApiOperation(value = "sketchBoard upload generate design前裁剪") @PostMapping("/sketchBoardsBoundingBox") public Response> sketchesBoundingBox(@Valid @RequestBody ReDesignCollectionDTO reDesignCollectionDTO) { @@ -83,4 +89,16 @@ public class DesignController { return Response.success(designService.getModel(designItemIdList)); } + @ApiOperation(value = "获取design结果") + @GetMapping("/getDesignResult") + public Response getDesignResult(@RequestParam("requestId") String requestId, @RequestParam("objectSignList") List objectSignList){ + return Response.success(designService.getDesignResult(requestId, objectSignList)); + } + + @ApiOperation(value = "云生成") + @PostMapping("/designCloud") + @CrossOrigin + public Response designCloud(@Valid @RequestBody DesignCollectionDTO designDTO) { + return Response.success(designService.designCloud(designDTO)); + } } diff --git a/src/main/java/com/ai/da/controller/DesignDetailController.java b/src/main/java/com/ai/da/controller/DesignDetailController.java index 994b0b2c..a02e0835 100644 --- a/src/main/java/com/ai/da/controller/DesignDetailController.java +++ b/src/main/java/com/ai/da/controller/DesignDetailController.java @@ -5,7 +5,6 @@ import com.ai.da.model.dto.*; import com.ai.da.model.vo.*; import com.ai.da.service.DesignItemService; import com.ai.da.service.DesignService; -import com.ai.da.service.UserLikeService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; @@ -15,8 +14,6 @@ import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.validation.Valid; import java.io.IOException; -import java.util.List; -import java.util.Map; @Api(tags = "design Detail模块") diff --git a/src/main/java/com/ai/da/controller/ElementController.java b/src/main/java/com/ai/da/controller/ElementController.java index 40dbcde1..8065c6dc 100644 --- a/src/main/java/com/ai/da/controller/ElementController.java +++ b/src/main/java/com/ai/da/controller/ElementController.java @@ -66,6 +66,8 @@ public class ElementController { return Response.success(); } + /** 该功能已删除 */ + @Deprecated @ApiOperation(value = "生成印花") @PostMapping("/generatePrint") public Response generatePrint(@Valid @RequestBody CollectionGeneratePrintDTO generatePrintDTO) { diff --git a/src/main/java/com/ai/da/controller/OrderInfoController.java b/src/main/java/com/ai/da/controller/OrderInfoController.java index 3a9ce1d5..56e60a48 100644 --- a/src/main/java/com/ai/da/controller/OrderInfoController.java +++ b/src/main/java/com/ai/da/controller/OrderInfoController.java @@ -3,9 +3,10 @@ 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.model.vo.OrderListVO; import com.ai.da.service.OrderInfoService; +import com.ai.da.service.PaymentInfoService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.*; @@ -22,10 +23,13 @@ public class OrderInfoController { @Resource private OrderInfoService orderInfoService; + @Resource + private PaymentInfoService paymentInfoService; + @ApiOperation("订单列表") @PostMapping("/list") - public Response> list(@Valid @RequestBody QueryPageByTimeDTO queryPageByTimeDTO){ - PageBaseResponse orderByAccountId = orderInfoService.getOrderByPage(queryPageByTimeDTO); + public Response> list(@Valid @RequestBody QueryPageByTimeDTO queryPageByTimeDTO){ + PageBaseResponse orderByAccountId = paymentInfoService.getPaymentInfo(queryPageByTimeDTO); // List list = orderInfoService.listOrderByCreateTimeDesc(); return Response.success(orderByAccountId); } diff --git a/src/main/java/com/ai/da/controller/PayPalCheckoutController.java b/src/main/java/com/ai/da/controller/PayPalCheckoutController.java index 203da0e7..8e3eda9a 100644 --- a/src/main/java/com/ai/da/controller/PayPalCheckoutController.java +++ b/src/main/java/com/ai/da/controller/PayPalCheckoutController.java @@ -1,6 +1,7 @@ package com.ai.da.controller; import com.ai.da.common.response.Response; +import com.ai.da.model.dto.ProductPurchaseDTO; import com.ai.da.service.PayPalCheckoutService; import com.paypal.http.HttpResponse; import com.paypal.http.exceptions.SerializeException; @@ -14,6 +15,7 @@ import javax.annotation.Resource; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; import java.io.IOException; import java.util.HashMap; @@ -26,9 +28,9 @@ public class PayPalCheckoutController { 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); + @PostMapping(value = "/trade") + public Response> createOrder(@Valid @RequestBody ProductPurchaseDTO productPurchaseDTO, HttpServletRequest request) throws SerializeException { + HashMap approvalUrl = payPalCheckoutService.createOrder(productPurchaseDTO, request); return Response.success(approvalUrl); } diff --git a/src/main/java/com/ai/da/controller/SavedCollectionController.java b/src/main/java/com/ai/da/controller/SavedCollectionController.java index 1967b5c5..bcd07da1 100644 --- a/src/main/java/com/ai/da/controller/SavedCollectionController.java +++ b/src/main/java/com/ai/da/controller/SavedCollectionController.java @@ -251,4 +251,10 @@ public class SavedCollectionController { public Response download() { return Response.success(userLikeGroupService.download()); } + + @ApiOperation(value = "productImageInitialize") + @PostMapping("/productImageInitialize") + public Response productImageUpload(@Valid @RequestBody ProductImageInitializeDTO productImageInitializeDTO) { + return Response.success(userLikeGroupService.productImageInitialize(productImageInitializeDTO)); + } } diff --git a/src/main/java/com/ai/da/controller/StripeController.java b/src/main/java/com/ai/da/controller/StripeController.java index 70430fab..2cbbbf1f 100644 --- a/src/main/java/com/ai/da/controller/StripeController.java +++ b/src/main/java/com/ai/da/controller/StripeController.java @@ -1,47 +1,55 @@ package com.ai.da.controller; import com.ai.da.common.response.Response; +import com.ai.da.model.dto.ProductPurchaseDTO; import com.ai.da.service.StripeService; import com.paypal.http.HttpResponse; import com.paypal.payments.Refund; +import com.stripe.exception.StripeException; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; +import springfox.documentation.annotations.ApiIgnore; import javax.annotation.Resource; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; import java.io.IOException; +import java.util.List; +import java.util.Map; @Api(tags = "Stripe模块") @Slf4j @RestController @RequestMapping("/api/stripe") +@ApiIgnore public class StripeController { @Resource private StripeService stripeService; @ApiOperation("创建支付链接") - @PostMapping("/createOrder/{amount}") - public Response pay(@PathVariable Integer amount, @RequestParam String returnUrl) { - return Response.success(stripeService.pay(amount, returnUrl)); + @PostMapping("/createOrder") + public Response pay(@Valid @RequestBody ProductPurchaseDTO productPurchaseDTO, HttpServletRequest request) { + return Response.success(stripeService.pay(productPurchaseDTO, request)); } @ApiOperation("支付通知") @PostMapping("/trade/notify") - public Response callback(HttpServletRequest request) throws ServletException, IOException { + public void callback(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Boolean result = stripeService.notify(request); if (result){ - return Response.success(200,"success"); + response.setStatus(HttpServletResponse.SC_OK); }else { - return Response.fail(400,"failure"); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } @ApiOperation("申请退款") - @PostMapping("/trade/refund/{orderNo}/{reason}") + @GetMapping("/trade/refund/{orderNo}/{reason}") public Response> refund(@PathVariable String orderNo, @PathVariable String reason) throws IOException { String response = stripeService.refund(null,orderNo,reason); if (response.equals("退款成功")){ @@ -51,4 +59,58 @@ public class StripeController { } } + @ApiOperation("获取订阅") + @GetMapping("/getSubscription") + public Response> getSubscription(@RequestParam String name, @RequestParam String email) { + try { + return Response.success(stripeService.getSubscriptionIds(name, email)); + } catch (StripeException e) { + throw new RuntimeException(e); + } + } + + @ApiOperation("取消订阅") + @GetMapping("/cancelSubscription") + public Response cancelSubscription(@RequestParam String subscriptionId, @RequestParam(required = false) String reason) { + stripeService.cancelSubscription(subscriptionId, reason); + return Response.success("success"); + } + /*@ApiOperation("临时 取消订阅") + @GetMapping("/cancelSubscriptionTemp") + public Response cancelSubscriptionTemp(@RequestParam String subscriptionId) { + stripeService.cancelSubscriptionTemp(subscriptionId); + return Response.success("success"); + } + + @ApiOperation("创建订阅 临时") + @GetMapping("/createSubscriptionTemp") + public Response createSubscriptionTemp(@RequestParam String name, @RequestParam String email) { + return Response.success(stripeService.createSubscriptionTemp(name, email)); + } + + @ApiOperation("修改用户默认支付方式 临时") + @GetMapping("/changeCustomerPayment") + public Response changeCustomerPayment(@RequestParam String name, @RequestParam String email) { + return Response.success(stripeService.changeCustomerPayment(name, email)); + } + + @ApiOperation("临时 发送续订失败邮件") + @GetMapping("/sendRenewalFailEmail") + public Response sendRenewalFailEmail(@RequestParam String invoiceId, @RequestParam String subscriptionId, @RequestParam String orderNo) { + return Response.success(stripeService.sendRenewalFailEmail(invoiceId, subscriptionId,orderNo)); + } + + @ApiOperation("临时 查询指定用户绑定的付款方式") + @GetMapping("/getCustomerPaymentMethod") + public Response>> getCustomerPaymentMethod(@RequestParam String name, @RequestParam String email) { + return Response.success(stripeService.getCustomerPaymentMethod(name, email)); + } + + @ApiOperation("临时 解绑指定用户绑定的所有付款方式") + @GetMapping("/detachCustomerAllPaymentMethod") + public Response detachCustomerAllPaymentMethod(@RequestParam String name, @RequestParam String email) { + return Response.success(stripeService.detachCustomerAllPaymentMethod(name, email)); + }*/ + + } diff --git a/src/main/java/com/ai/da/controller/TagsController.java b/src/main/java/com/ai/da/controller/TagsController.java index 8a2b3209..a248cfcd 100644 --- a/src/main/java/com/ai/da/controller/TagsController.java +++ b/src/main/java/com/ai/da/controller/TagsController.java @@ -25,7 +25,7 @@ public class TagsController { @ApiOperation("获取标签") @GetMapping("/getTags") - public Response> getTags(@RequestParam("userInput") String userInput) { + public Response> getTags(@RequestParam(value = "userInput", required = false) String userInput) { return Response.success(tagsService.getTags(userInput)); } } diff --git a/src/main/java/com/ai/da/controller/ThirdPartyController.java b/src/main/java/com/ai/da/controller/ThirdPartyController.java index e077fa8c..72454016 100644 --- a/src/main/java/com/ai/da/controller/ThirdPartyController.java +++ b/src/main/java/com/ai/da/controller/ThirdPartyController.java @@ -1,9 +1,13 @@ package com.ai.da.controller; import com.ai.da.common.response.Response; +import com.ai.da.mapper.primary.entity.GoogleUser; import com.ai.da.model.dto.*; import com.ai.da.model.vo.AccountLoginVO; +import com.ai.da.model.vo.DesignCollectionVO; import com.ai.da.service.AccountService; +import com.ai.da.service.DesignService; +import com.alibaba.fastjson.JSONObject; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; @@ -28,6 +32,9 @@ public class ThirdPartyController { @Resource private AccountService accountService; + @Resource + private DesignService designService; + /*@ApiOperation(value = "Add user information") @PostMapping("/addUser") public Response addUser(@Valid @RequestBody AccountAddDTO accountAddDTO) { @@ -121,4 +128,22 @@ public class ThirdPartyController { public Response googleCallback(@RequestParam("code") String code, HttpSession session) { return Response.success(accountService.googleCallback(code, session)); } + @CrossOrigin + @GetMapping("/parseGoogleCredential") + public Response parseGoogleCredential(@RequestParam("credential") String credential, @RequestParam("type") Integer type) { + return Response.success(accountService.parseGoogleCredential(credential, type)); + } + + @CrossOrigin + @GetMapping("/parseWeChatCode") + public Response parseWeChatCode(@RequestParam("code") String code, @RequestParam("type") Integer type) { + return Response.success(accountService.parseWeChatCode(code, type)); + } + + @ApiOperation(value = "接收Design结果") + @PostMapping("/receiveDesignResults") + @CrossOrigin + public Response receiveDesignResults(@Valid @RequestBody JSONObject responseObject) { + return Response.success(designService.receiveDesignResults(responseObject)); + } } diff --git a/src/main/java/com/ai/da/mapper/primary/entity/AccountExtendMapper.java b/src/main/java/com/ai/da/mapper/primary/AccountExtendMapper.java similarity index 74% rename from src/main/java/com/ai/da/mapper/primary/entity/AccountExtendMapper.java rename to src/main/java/com/ai/da/mapper/primary/AccountExtendMapper.java index 312fa810..687790e3 100644 --- a/src/main/java/com/ai/da/mapper/primary/entity/AccountExtendMapper.java +++ b/src/main/java/com/ai/da/mapper/primary/AccountExtendMapper.java @@ -1,6 +1,7 @@ -package com.ai.da.mapper.primary.entity; +package com.ai.da.mapper.primary; import com.ai.da.common.config.mybatis.plus.CommonMapper; +import com.ai.da.mapper.primary.entity.AccountExtend; import java.util.Date; import java.util.List; diff --git a/src/main/java/com/ai/da/mapper/primary/AccountMapper.java b/src/main/java/com/ai/da/mapper/primary/AccountMapper.java index d8422ece..8826eb71 100644 --- a/src/main/java/com/ai/da/mapper/primary/AccountMapper.java +++ b/src/main/java/com/ai/da/mapper/primary/AccountMapper.java @@ -30,6 +30,6 @@ public interface AccountMapper extends CommonMapper { */ // Account findById(String id); - void toVisitor(Long id, Date date); + void toVisitor(Long id); } diff --git a/src/main/java/com/ai/da/mapper/primary/AffiliateIncomeMapper.java b/src/main/java/com/ai/da/mapper/primary/AffiliateIncomeMapper.java new file mode 100644 index 00000000..d0a7d6ce --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/AffiliateIncomeMapper.java @@ -0,0 +1,14 @@ +package com.ai.da.mapper.primary; + +import com.ai.da.mapper.primary.entity.AffiliateIncome; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import java.util.List; +import java.util.Map; + +public interface AffiliateIncomeMapper extends BaseMapper { + + List> getPersonalMonthlyIncome(Long affiliateAccountId, int year); + + List> getMonthlyAffiliateIncome(int year, int month); +} diff --git a/src/main/java/com/ai/da/mapper/primary/AffiliateMapper.java b/src/main/java/com/ai/da/mapper/primary/AffiliateMapper.java new file mode 100644 index 00000000..1a53ca9f --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/AffiliateMapper.java @@ -0,0 +1,19 @@ +package com.ai.da.mapper.primary; + +import com.ai.da.mapper.primary.entity.Affiliate; +import com.ai.da.model.vo.AffiliateVO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import java.util.List; +import java.util.Map; + +public interface AffiliateMapper extends BaseMapper { + + Map getMonthlyApprovedAffiliate(int year, int month); + + List getAffiliateList(String status, String startTime, String endTime, + String order, Long affiliateId, int size, int offset); + + int queryAffiliateTotalCount(String status, String startTime, String endTime, Long affiliateId); + +} diff --git a/src/main/java/com/ai/da/mapper/primary/DesignBatchMapper.java b/src/main/java/com/ai/da/mapper/primary/DesignBatchMapper.java new file mode 100644 index 00000000..1c8f2b65 --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/DesignBatchMapper.java @@ -0,0 +1,16 @@ +package com.ai.da.mapper.primary; + +import com.ai.da.common.config.mybatis.plus.CommonMapper; +import com.ai.da.mapper.primary.entity.AccountExtend; +import com.ai.da.mapper.primary.entity.DesignBatch; + +/** + * Mapper 接口 + * + * @author easy-generator + * @since 2022-06-13 + */ +public interface DesignBatchMapper extends CommonMapper { + + +} diff --git a/src/main/java/com/ai/da/mapper/primary/MoodboardPositionMapper.java b/src/main/java/com/ai/da/mapper/primary/MoodboardPositionMapper.java new file mode 100644 index 00000000..1ea6f067 --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/MoodboardPositionMapper.java @@ -0,0 +1,7 @@ +package com.ai.da.mapper.primary; + +import com.ai.da.common.config.mybatis.plus.CommonMapper; +import com.ai.da.mapper.primary.entity.MoodboardPosition; + +public interface MoodboardPositionMapper extends CommonMapper { +} diff --git a/src/main/java/com/ai/da/mapper/primary/PaymentInfoMapper.java b/src/main/java/com/ai/da/mapper/primary/PaymentInfoMapper.java index 27b147a4..c6721dd1 100644 --- a/src/main/java/com/ai/da/mapper/primary/PaymentInfoMapper.java +++ b/src/main/java/com/ai/da/mapper/primary/PaymentInfoMapper.java @@ -1,7 +1,29 @@ package com.ai.da.mapper.primary; import com.ai.da.mapper.primary.entity.PaymentInfo; +import com.ai.da.model.vo.OrderListVO; +import com.ai.da.model.vo.PaymentInfoVO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import java.util.List; +import java.util.Map; + public interface PaymentInfoMapper extends BaseMapper { + + List selectPageOrderList(Long accountId, String startTime, String endTime, int offset, int pageSize, Long id); + + int queryOrderListTotalCount(Long accountId, String startTime, String endTime, Long id); + + List queryPaymentInfo(String paymentType,String payerTotal, String type, String status, + String country, String city, String startTime, String endTime, + int limit, int offset, String order, String payer + ); + + Long queryPaymentInfoCount(String paymentType,String payerTotal, String type, String status, + String country, String city, String startTime, String endTime, String payer + ); + + List> getCities(); + + List> getCountries(); } diff --git a/src/main/java/com/ai/da/mapper/primary/ProductImageAttributeMapper.java b/src/main/java/com/ai/da/mapper/primary/ProductImageAttributeMapper.java new file mode 100644 index 00000000..65d0551a --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/ProductImageAttributeMapper.java @@ -0,0 +1,7 @@ +package com.ai.da.mapper.primary; + +import com.ai.da.common.config.mybatis.plus.CommonMapper; +import com.ai.da.mapper.primary.entity.ProductImageAttribute; + +public interface ProductImageAttributeMapper extends CommonMapper { +} diff --git a/src/main/java/com/ai/da/mapper/primary/SubscriptionInfoMapper.java b/src/main/java/com/ai/da/mapper/primary/SubscriptionInfoMapper.java new file mode 100644 index 00000000..8a45bb7e --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/SubscriptionInfoMapper.java @@ -0,0 +1,8 @@ +package com.ai.da.mapper.primary; + +import com.ai.da.mapper.primary.entity.SubscriptionInfo; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface SubscriptionInfoMapper extends BaseMapper { + +} diff --git a/src/main/java/com/ai/da/mapper/primary/UserLikeSortMapper.java b/src/main/java/com/ai/da/mapper/primary/UserLikeSortMapper.java new file mode 100644 index 00000000..c6966249 --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/UserLikeSortMapper.java @@ -0,0 +1,7 @@ +package com.ai.da.mapper.primary; + +import com.ai.da.common.config.mybatis.plus.CommonMapper; +import com.ai.da.mapper.primary.entity.UserLikeSort; + +public interface UserLikeSortMapper extends CommonMapper { +} 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 285acd90..1dbebd7f 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 @@ -57,6 +57,11 @@ public class Account implements Serializable { */ private String country; + /** + * 职业 + */ + private String occupation; + /** * 账户有效期开始时间 */ @@ -97,6 +102,10 @@ public class Account implements Serializable { * 2 : 月付用户 * 3 : 试用用户 * 4 : 参加活动获取30天有效期和6000个积分的用户 + * 5 : 企业管理员账号 + * 6 : 企业子账号 + * 7 : 学校管理员 + * 8 : 学校子账号 */ private Integer systemUser; @@ -104,4 +113,16 @@ public class Account implements Serializable { * 头像 */ private String avatar; + + private String organizationName; + + private Long parentId; + + private Integer isAdmin; + + private BigDecimal shareCredits; + + private Integer subAccountNum; + + private String invitationCode; } diff --git a/src/main/java/com/ai/da/mapper/primary/entity/AccountExtend.java b/src/main/java/com/ai/da/mapper/primary/entity/AccountExtend.java index 5742f0ec..c5b612b1 100644 --- a/src/main/java/com/ai/da/mapper/primary/entity/AccountExtend.java +++ b/src/main/java/com/ai/da/mapper/primary/entity/AccountExtend.java @@ -1,5 +1,6 @@ package com.ai.da.mapper.primary.entity; +import com.ai.da.common.response.PageResponse; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; @@ -26,5 +27,9 @@ public class AccountExtend implements Serializable { private String authType; + private String headImgUrl; + + private String name; + private String auth; } diff --git a/src/main/java/com/ai/da/mapper/primary/entity/Affiliate.java b/src/main/java/com/ai/da/mapper/primary/entity/Affiliate.java new file mode 100644 index 00000000..beb52662 --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/entity/Affiliate.java @@ -0,0 +1,30 @@ +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_affiliate") +public class Affiliate extends BaseEntity{ + + private Long accountId; + + // Active(活跃) || Inactive(过期) || Pending(待审批) || Refused(拒绝) + private String status; + + private Float totalEarnings = 0.00F; + + private Float monthlyEarnings = 0.00F; + + private Float unpaidEarnings = 0.00F; + + private Integer visits = 0; + + private Boolean approved = false; + + private String link; + + private String promotionMethod; +} diff --git a/src/main/java/com/ai/da/mapper/primary/entity/AffiliateIncome.java b/src/main/java/com/ai/da/mapper/primary/entity/AffiliateIncome.java new file mode 100644 index 00000000..d2d276c4 --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/entity/AffiliateIncome.java @@ -0,0 +1,28 @@ +package com.ai.da.mapper.primary.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("t_affiliate_income") +public class AffiliateIncome extends BaseEntity { + + private Long affiliateId; + + private Long affiliateAccountId; + + private Long inviteeAccountId; + + private Float amount; + + private Long paymentInfoId; + + private LocalDateTime paymentTime; + + private Float commission; + +} diff --git a/src/main/java/com/ai/da/mapper/primary/entity/DesignBatch.java b/src/main/java/com/ai/da/mapper/primary/entity/DesignBatch.java new file mode 100644 index 00000000..3ad4dbb5 --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/entity/DesignBatch.java @@ -0,0 +1,44 @@ +package com.ai.da.mapper.primary.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.models.auth.In; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@TableName("design_batch") +public class DesignBatch implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + private Long accountId; // account_id + + private Long designId; // design_id + + private Long collectionId; // collection_id + + private Integer status; // status + + private LocalDateTime createTime; // create_time + + private LocalDateTime updateTime; // update_time + + private String taskId; // task_id + + private Integer totalNum; // total_num + + private Integer completedNum; // completed_num +} diff --git a/src/main/java/com/ai/da/mapper/primary/entity/MoodboardPosition.java b/src/main/java/com/ai/da/mapper/primary/entity/MoodboardPosition.java new file mode 100644 index 00000000..db58f7d3 --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/entity/MoodboardPosition.java @@ -0,0 +1,29 @@ +package com.ai.da.mapper.primary.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@TableName("moodboard_position") +public class MoodboardPosition implements Serializable { + private static final long serialVersionUID = 1L; + @TableId(value = "id", type = IdType.AUTO) + private Long id; + private Long moodboardId; + private Long collectionId; + private String type; + private String styleData; + private Integer sequence; + private LocalDateTime createTime; + private LocalDateTime updateTime; + +} 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 98a020b9..15ae0cee 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 @@ -21,5 +21,18 @@ public class OrderInfo extends BaseEntity{ private String orderStatus;//订单状态 + private String note; + private String paymentType;//支付方式 + + // 可用于标记用户订单是否首次订阅 + private byte isFirstSubscription = 0; + + private byte isCommissionCalculated = 0; + + private String ipAddress; + + private String country; + + private String city; } diff --git a/src/main/java/com/ai/da/mapper/primary/entity/PaymentInfo.java b/src/main/java/com/ai/da/mapper/primary/entity/PaymentInfo.java index c3876106..12470d20 100644 --- a/src/main/java/com/ai/da/mapper/primary/entity/PaymentInfo.java +++ b/src/main/java/com/ai/da/mapper/primary/entity/PaymentInfo.java @@ -2,7 +2,9 @@ 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_payment_info") public class PaymentInfo extends BaseEntity{ @@ -13,11 +15,36 @@ public class PaymentInfo extends BaseEntity{ private String paymentType;//支付类型 - private String tradeType;//交易类型 - + /** + * PayPal 订单状态:CREATED/SAVED/APPROVED/VOIDED/COMPLETED/PAYER_ACTION_REQUIRED + * Stripe 订单状态: 原 session 状态:open/completed/expired ; 现 invoice 状态:draft/open/paid/uncollectible/void + * Alipay-HK 订单状态:wait, paid, expired, liquidated + * paid and liquidated means the refund request has been executed. + * expired means the request has been rejected. + * wait means the request is still under processing. + */ private String tradeState;//交易状态 private Float payerTotal;//支付金额(元) private String content;//通知参数 + + // 支付类型 new || renewal || credits + private String type; + + // 当前支付是否已邮件通知 0 || 1 + private Integer notified; + + private String paymentMethod; + + private String last4; + + // 发票托管页面 + private String hostedInvoiceUrl; + + private String ipAddress; + + private String country; + + private String city; } diff --git a/src/main/java/com/ai/da/mapper/primary/entity/ProductImageAttribute.java b/src/main/java/com/ai/da/mapper/primary/entity/ProductImageAttribute.java new file mode 100644 index 00000000..129eb32b --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/entity/ProductImageAttribute.java @@ -0,0 +1,35 @@ +package com.ai.da.mapper.primary.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@TableName("product_image_attribute") +public class ProductImageAttribute implements Serializable { + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Long id; + private String imgName; + private String length; + private String sleeveLength; + private String sleeveShape; + private String sleeveShoulder; + private String neckline; + private String collar; + private String design; + private String silhouette; + private String type; + private String openingType; + private String subtype; + + private String style; +} diff --git a/src/main/java/com/ai/da/mapper/primary/entity/SubscriptionInfo.java b/src/main/java/com/ai/da/mapper/primary/entity/SubscriptionInfo.java new file mode 100644 index 00000000..7ee32ab0 --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/entity/SubscriptionInfo.java @@ -0,0 +1,39 @@ +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_subscription_info") +public class SubscriptionInfo extends BaseEntity{ + + private Long accountId; + + private String orderNo; + + // stripe || paypal 平台生成的id + private String subscriptionId; + + // month || year + private String type; + + // active || expired + private String status = "active"; + + private byte cancelNotified = (byte)0; + + // 续订的下一个付款日 + private String nextPayDate; + + // 当前订阅订单有效期开始时间 + private Long currentPeriodStart; + + // 当前订阅订单有效期结束时间 + private Long currentPeriodEnd; + + // 取消订阅原因 + private String cancelReason; + +} diff --git a/src/main/java/com/ai/da/mapper/primary/entity/Tags.java b/src/main/java/com/ai/da/mapper/primary/entity/Tags.java index fc7dfd90..7ffe7a62 100644 --- a/src/main/java/com/ai/da/mapper/primary/entity/Tags.java +++ b/src/main/java/com/ai/da/mapper/primary/entity/Tags.java @@ -10,4 +10,7 @@ import lombok.EqualsAndHashCode; public class Tags extends BaseEntity{ private String tagName; + + // 表示标签是否正在活动中 0->不在活动中 1->在活动中 + private byte active = (byte)0; } diff --git a/src/main/java/com/ai/da/mapper/primary/entity/UserLikeSort.java b/src/main/java/com/ai/da/mapper/primary/entity/UserLikeSort.java new file mode 100644 index 00000000..3e733d3c --- /dev/null +++ b/src/main/java/com/ai/da/mapper/primary/entity/UserLikeSort.java @@ -0,0 +1,24 @@ +package com.ai.da.mapper.primary.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.Date; +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@TableName("user_like_sort") +public class UserLikeSort implements Serializable { + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Long id; + private Long userLikeGroupId; + private Long userLikeId; + private Integer sort; +} \ No newline at end of file diff --git a/src/main/java/com/ai/da/mapper/secondary/entity/AttributeRetrieval.java b/src/main/java/com/ai/da/mapper/secondary/entity/AttributeRetrieval.java index 84cc1b91..12ef2329 100644 --- a/src/main/java/com/ai/da/mapper/secondary/entity/AttributeRetrieval.java +++ b/src/main/java/com/ai/da/mapper/secondary/entity/AttributeRetrieval.java @@ -19,6 +19,4 @@ public class AttributeRetrieval { private String subtype; private String style; - - private Integer deprecated; } diff --git a/src/main/java/com/ai/da/model/dto/AccountBindEmailDTO.java b/src/main/java/com/ai/da/model/dto/AccountBindEmailDTO.java index 8671e051..b5559769 100644 --- a/src/main/java/com/ai/da/model/dto/AccountBindEmailDTO.java +++ b/src/main/java/com/ai/da/model/dto/AccountBindEmailDTO.java @@ -5,15 +5,14 @@ import io.swagger.annotations.ApiModelProperty; import lombok.Data; import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; @Data @ApiModel("绑定邮箱") public class AccountBindEmailDTO { - @NotNull(message = "userId.cannot.be.empty") - @ApiModelProperty("用户id") - private Long userId; +// @NotNull(message = "userId.cannot.be.empty") +// @ApiModelProperty("用户id") +// private Long userId; @NotBlank(message = "email.cannot.be.empty") @ApiModelProperty("邮箱") diff --git a/src/main/java/com/ai/da/model/dto/AccountDesignWorksRegisterDTO.java b/src/main/java/com/ai/da/model/dto/AccountDesignWorksRegisterDTO.java index db2dac3b..27320588 100644 --- a/src/main/java/com/ai/da/model/dto/AccountDesignWorksRegisterDTO.java +++ b/src/main/java/com/ai/da/model/dto/AccountDesignWorksRegisterDTO.java @@ -6,4 +6,6 @@ import lombok.Data; @Data public class AccountDesignWorksRegisterDTO extends Account { private String emailVerifyCode; + +// private String invitationCode; } diff --git a/src/main/java/com/ai/da/model/dto/AccountLoginDTO.java b/src/main/java/com/ai/da/model/dto/AccountLoginDTO.java index 2c1483c9..2f536ffb 100644 --- a/src/main/java/com/ai/da/model/dto/AccountLoginDTO.java +++ b/src/main/java/com/ai/da/model/dto/AccountLoginDTO.java @@ -32,4 +32,6 @@ public class AccountLoginDTO { @ApiModelProperty("邮箱验证码") private String emailVerifyCode; + private String organizationName; + } diff --git a/src/main/java/com/ai/da/model/dto/AddSubAccountDTO.java b/src/main/java/com/ai/da/model/dto/AddSubAccountDTO.java new file mode 100644 index 00000000..2f894789 --- /dev/null +++ b/src/main/java/com/ai/da/model/dto/AddSubAccountDTO.java @@ -0,0 +1,12 @@ +package com.ai.da.model.dto; + +import com.ai.da.mapper.primary.entity.Account; +import lombok.Data; + +import java.util.List; + +@Data +public class AddSubAccountDTO extends Account { + + private List deleteIdList; +} diff --git a/src/main/java/com/ai/da/model/dto/AffiliateEmailParamsDTO.java b/src/main/java/com/ai/da/model/dto/AffiliateEmailParamsDTO.java new file mode 100644 index 00000000..38c87b72 --- /dev/null +++ b/src/main/java/com/ai/da/model/dto/AffiliateEmailParamsDTO.java @@ -0,0 +1,31 @@ +package com.ai.da.model.dto; + +import lombok.Data; + +@Data +public class AffiliateEmailParamsDTO { + + private String username; + + private String promotionMethod; + + private String totalProgramRevenue; + + private String newApprovedAffiliates; + + private String unpaidEarnings; + + private String paidEarnings; + + public AffiliateEmailParamsDTO() { + } + + public AffiliateEmailParamsDTO(String username) { + this.username = username; + } + + public AffiliateEmailParamsDTO(String username, String promotionMethod) { + this.username = username; + this.promotionMethod = promotionMethod; + } +} diff --git a/src/main/java/com/ai/da/model/dto/AffiliateQueryDTO.java b/src/main/java/com/ai/da/model/dto/AffiliateQueryDTO.java new file mode 100644 index 00000000..79fac3e7 --- /dev/null +++ b/src/main/java/com/ai/da/model/dto/AffiliateQueryDTO.java @@ -0,0 +1,31 @@ +package com.ai.da.model.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +@ApiModel("查询affiliate列表") +public class AffiliateQueryDTO extends TimeQueryBaseDTO{ + @ApiModelProperty("Active(活跃) || Inactive(过期) || Pending(待审批) || Refused(拒绝)") + private String status; + + @ApiModelProperty("推广者id") + private Long affiliateId; + + @ApiModelProperty("按时间 DESC 降序 || ASC 升序") + private String order = "ASC"; + + @Override + public String toString() { + return "AffiliateQueryDTO{" + + "status='" + status + '\'' + ' ' + + "startTime='" + super.getStartTime() + '\'' + ' ' + + "endTime='" + super.getEndTime() + '\'' + ' ' + + "page='" + super.getPage() + '\'' + ' ' + + "size='" + super.getSize() + '\'' + ' ' + + '}'; + } +} diff --git a/src/main/java/com/ai/da/model/dto/DesignCollectionDTO.java b/src/main/java/com/ai/da/model/dto/DesignCollectionDTO.java index cad8e572..54314307 100644 --- a/src/main/java/com/ai/da/model/dto/DesignCollectionDTO.java +++ b/src/main/java/com/ai/da/model/dto/DesignCollectionDTO.java @@ -63,4 +63,8 @@ public class DesignCollectionDTO { private String moodboardPosition; + private List requestIdList; + + private Integer designNum; + } diff --git a/src/main/java/com/ai/da/model/dto/EmailSendDTO.java b/src/main/java/com/ai/da/model/dto/EmailSendDTO.java index eed614bb..946946bd 100644 --- a/src/main/java/com/ai/da/model/dto/EmailSendDTO.java +++ b/src/main/java/com/ai/da/model/dto/EmailSendDTO.java @@ -15,10 +15,17 @@ public class EmailSendDTO { private String email; @NotBlank(message = "operationType.cannot.be.empty") - @ApiModelProperty("操作类型 LOGIN 注册 FORGET_PWD 忘记密码 BIND_MAILBOX 绑定邮箱 CHANGE_MAILBOX 更改邮箱") + @ApiModelProperty("操作类型 LOGIN 注册 FORGET_PWD 忘记密码 BIND_MAILBOX 绑定邮箱 " + + "CHANGE_MAILBOX 更改邮箱 UPDATE_USERINFO 仅填写国家、职业(不发送邮件)") private String operationType; @ApiModelProperty("异常ip") private String ip; + @ApiModelProperty("国家") + private String country; + + @ApiModelProperty("职业") + private String occupation; + } diff --git a/src/main/java/com/ai/da/model/dto/GenerateThroughImageTextDTO.java b/src/main/java/com/ai/da/model/dto/GenerateThroughImageTextDTO.java index 8dbeca50..772cb8cf 100644 --- a/src/main/java/com/ai/da/model/dto/GenerateThroughImageTextDTO.java +++ b/src/main/java/com/ai/da/model/dto/GenerateThroughImageTextDTO.java @@ -12,49 +12,50 @@ import javax.validation.constraints.NotNull; public class GenerateThroughImageTextDTO { @NotNull(message = "userId cannot be empty") @ApiModelProperty("用户id") - Long userId; + private Long userId; @ApiModelProperty("caption | prompt") - String text; + private String text; @ApiModelProperty("图片在t_collection_element表中的id") - Long collectionElementId; + private Long collectionElementId; // todo 后续取消这个字段的传输,由后端自行判断相关参数是否有值 // @NotBlank(message = "you have to choose the generate type") @ApiModelProperty("text image text-image") - String generateType; + private String generateType; @ApiModelProperty("图片来源:update,从library中选择,从toProductImage结果中选择 collection || library || productImage") - String designType; + private String designType; @NotBlank(message = "level1Type cannot be empty!") @ApiModelProperty("Moodboard Printboard Sketchboard MarketingSketch") - String level1Type; + private String level1Type; @ApiModelProperty("Outwear Dress Blouse Skirt Trousers || Logo Slogan Pattern") - String level2Type; + private String level2Type; @ApiModelProperty("性别") - String gender; + private String gender; - @ApiModelProperty("选择的模型名") - String version; + + @ApiModelProperty("选择的模型名 high || fast") + private String version; @NotBlank(message = "timeZone cannot be empty!") @ApiModelProperty("本地时区,比如 'Asia/Tokyo' 东京时间 , 'Asia/Shanghai' 北京时间 由js本地获取") - String timeZone; + private String timeZone; @ApiModelProperty("唯一id,用于保持消息唯一性") - String uniqueId; + private String uniqueId; @NotNull(message = "Please check if the required fields are empty.(isTestUser)") @ApiModelProperty("是否是测试用户") - Boolean isTestUser; + private Boolean isTestUser; @ApiModelProperty("页面上用户设计的slogan所截的图片") - String sloganBase64; + private String sloganBase64; @ApiModelProperty("种子 取值范围 0~500") - String seed; + private String seed; } diff --git a/src/main/java/com/ai/da/model/dto/ProductImageInitializeDTO.java b/src/main/java/com/ai/da/model/dto/ProductImageInitializeDTO.java new file mode 100644 index 00000000..a3aace06 --- /dev/null +++ b/src/main/java/com/ai/da/model/dto/ProductImageInitializeDTO.java @@ -0,0 +1,10 @@ +package com.ai.da.model.dto; + +import lombok.Data; + +import java.util.List; + +@Data +public class ProductImageInitializeDTO { + private List libraryIds; +} diff --git a/src/main/java/com/ai/da/model/dto/ProductPurchaseDTO.java b/src/main/java/com/ai/da/model/dto/ProductPurchaseDTO.java new file mode 100644 index 00000000..371439f9 --- /dev/null +++ b/src/main/java/com/ai/da/model/dto/ProductPurchaseDTO.java @@ -0,0 +1,33 @@ +package com.ai.da.model.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +@Data +@ApiModel("购买产品DTO") +public class ProductPurchaseDTO { + + @ApiModelProperty("购买数量") + private int quantity; + + // http://example.com + @NotBlank(message = "return url cannot be empty") + @ApiModelProperty("购买完成后返回页面地址") + private String returnUrl; + + @NotBlank(message = "product name cannot be empty") + @ApiModelProperty("产品名 CreditsPurchase || Subscription") + private String productName; + + @ApiModelProperty("Month || Year") + private String subscribeType; + + @ApiModelProperty("是否自动续订 one_time || recurring") + private Boolean autoRenewal; + + @ApiModelProperty("使用Alipay-HK时需要选择 ALIPAYHK || ALIPAYCN") + private String wallet; +} diff --git a/src/main/java/com/ai/da/model/dto/QueryPageByTimeDTO.java b/src/main/java/com/ai/da/model/dto/QueryPageByTimeDTO.java index c0c37a98..669493eb 100644 --- a/src/main/java/com/ai/da/model/dto/QueryPageByTimeDTO.java +++ b/src/main/java/com/ai/da/model/dto/QueryPageByTimeDTO.java @@ -16,4 +16,7 @@ public class QueryPageByTimeDTO extends PageQueryBaseVo { @ApiModelProperty("结束时间 yyyy-mm-dd hh:mm:ss 可以不要时分秒") private String endTime; + + @ApiModelProperty("指定id") + private Long id; } diff --git a/src/main/java/com/ai/da/model/dto/QueryPaymentInfoDTO.java b/src/main/java/com/ai/da/model/dto/QueryPaymentInfoDTO.java new file mode 100644 index 00000000..6aa95e9a --- /dev/null +++ b/src/main/java/com/ai/da/model/dto/QueryPaymentInfoDTO.java @@ -0,0 +1,30 @@ +package com.ai.da.model.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +@Data +@ApiModel("交易记录详情") +public class QueryPaymentInfoDTO extends QueryPageByTimeDTO { + @ApiModelProperty("选择的支付平台 PayPal || Stripe || Alipay-HK") + private String platform; + @ApiModelProperty("支付的金额 单位:HKD") + private String payerTotal; + @ApiModelProperty("商品种类 new || renewal || credits") + private String type; + @ApiModelProperty("交易状态 Success || Fail || Pending") + private String status; + @ApiModelProperty("付款人所在国家") + private String country; + @ApiModelProperty("付款人所在城市") + private String city; + @ApiModelProperty("按id排序 DESC || ASC") + private String order = "DESC"; + @ApiModelProperty("付款用户名") + private String payer; +} diff --git a/src/main/java/com/ai/da/model/dto/ReDesignCollectionDTO.java b/src/main/java/com/ai/da/model/dto/ReDesignCollectionDTO.java index b72a690a..8c8bbe48 100644 --- a/src/main/java/com/ai/da/model/dto/ReDesignCollectionDTO.java +++ b/src/main/java/com/ai/da/model/dto/ReDesignCollectionDTO.java @@ -33,7 +33,7 @@ public class ReDesignCollectionDTO { @ApiModelProperty("市场手稿板图片id 数组") private List marketingSketchs; - @NotNull(message = "colorBoards.cannot.be.empty") + @NotNull(message = "systemScale.cannot.be.empty") @ApiModelProperty("系统取图比列") private BigDecimal systemScale; @@ -67,4 +67,8 @@ public class ReDesignCollectionDTO { private String moodboardPosition; private String moodTemplateId; + + private List requestIdList; + + private Integer designNum; } diff --git a/src/main/java/com/ai/da/model/dto/SubAccountPageDTO.java b/src/main/java/com/ai/da/model/dto/SubAccountPageDTO.java new file mode 100644 index 00000000..f5dcc995 --- /dev/null +++ b/src/main/java/com/ai/da/model/dto/SubAccountPageDTO.java @@ -0,0 +1,9 @@ +package com.ai.da.model.dto; + +import com.ai.da.model.vo.PageQueryBaseVo; +import lombok.Data; + +@Data +public class SubAccountPageDTO extends PageQueryBaseVo { + private String userName; +} diff --git a/src/main/java/com/ai/da/model/dto/SubscriptionEmailParamsDTO.java b/src/main/java/com/ai/da/model/dto/SubscriptionEmailParamsDTO.java new file mode 100644 index 00000000..1d6f6fd7 --- /dev/null +++ b/src/main/java/com/ai/da/model/dto/SubscriptionEmailParamsDTO.java @@ -0,0 +1,55 @@ +package com.ai.da.model.dto; + +import lombok.Data; + +@Data +public class SubscriptionEmailParamsDTO { + // 用户名 + private String username; + + // t_payment_info id(每次支付对于用户来说是一笔新订单) + private String orderId; + + // 链接到订单列表的某个订单 + private String orderRef; + + // 订单支付创建日期 + private String createDate; + + // 购买数量 + private String quantity; + + // 费用 + private String totalFee; + + // 当前订阅开始时间 + private String lastOrderDate; + + // 当前订阅结束时间 + private String endOfPrepaidTerm; + + // 付款方式 + private String paymentMethod; + + private String last4; + + // 订阅Id + private String subscriptionId; + + // 订阅方式 + private String subscriptionType; + + // 订阅开始时间 + private String startDate; + + // 下一个支付日期 + private String nextPayDate; + + // 下次付款时间(reminder) + private String renewalTime; + + // 付款失败原因 + private String failMessage; + + +} diff --git a/src/main/java/com/ai/da/model/dto/TimeQueryBaseDTO.java b/src/main/java/com/ai/da/model/dto/TimeQueryBaseDTO.java new file mode 100644 index 00000000..3c10297b --- /dev/null +++ b/src/main/java/com/ai/da/model/dto/TimeQueryBaseDTO.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 TimeQueryBaseDTO extends PageQueryBaseVo { + + @ApiModelProperty("按时间区间查询 区间起点") + private String startTime; + + @ApiModelProperty("按时间区间查询 区间终点") + private String endTime; +} diff --git a/src/main/java/com/ai/da/model/dto/UserLikeSortDTO.java b/src/main/java/com/ai/da/model/dto/UserLikeSortDTO.java new file mode 100644 index 00000000..9cf6dd8c --- /dev/null +++ b/src/main/java/com/ai/da/model/dto/UserLikeSortDTO.java @@ -0,0 +1,13 @@ +package com.ai.da.model.dto; + +import com.ai.da.mapper.primary.entity.UserLikeSort; +import lombok.Data; + +import java.util.List; + +@Data +public class UserLikeSortDTO{ + private Long userLikeGroupId; + + List userLikeSortList; +} diff --git a/src/main/java/com/ai/da/model/vo/AccountLoginVO.java b/src/main/java/com/ai/da/model/vo/AccountLoginVO.java index ab23b872..02b2c73d 100644 --- a/src/main/java/com/ai/da/model/vo/AccountLoginVO.java +++ b/src/main/java/com/ai/da/model/vo/AccountLoginVO.java @@ -1,13 +1,14 @@ package com.ai.da.model.vo; +import com.ai.da.mapper.primary.entity.AccountExtend; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; -import io.swagger.models.auth.In; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import javax.validation.constraints.NotBlank; +import java.util.List; +import java.util.Map; @AllArgsConstructor @NoArgsConstructor @@ -40,4 +41,36 @@ public class AccountLoginVO { private Long followerCount; + private List accountExtendList; + + private Long validStartTime; + + private Long validEndTime; + + private String Language; + + // 订阅id(stripe提供) + private String subscriptionId; + + // 订阅状态 + private String status; + + // 订阅过期时间 + private String expireTime; + + // 订阅类型 month || year + private String subscriptionType; + + // 是否自动续订 + private boolean isAutoRenewal; + + // 是否是affiliate + private boolean isAffiliate = false; + + private String country; + + private String occupation; + + private Long usernameModify; + } diff --git a/src/main/java/com/ai/da/model/vo/AffiliateInvitationDetailsVO.java b/src/main/java/com/ai/da/model/vo/AffiliateInvitationDetailsVO.java new file mode 100644 index 00000000..4a9cdf54 --- /dev/null +++ b/src/main/java/com/ai/da/model/vo/AffiliateInvitationDetailsVO.java @@ -0,0 +1,25 @@ +package com.ai.da.model.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AffiliateInvitationDetailsVO { + + private Long accountId; + + private String username; + + private Float firstSubscriptionPaymentAmount; + + private Float commission; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8") + private LocalDateTime time; +} diff --git a/src/main/java/com/ai/da/model/vo/AffiliateVO.java b/src/main/java/com/ai/da/model/vo/AffiliateVO.java new file mode 100644 index 00000000..eedf5481 --- /dev/null +++ b/src/main/java/com/ai/da/model/vo/AffiliateVO.java @@ -0,0 +1,17 @@ +package com.ai.da.model.vo; + +import com.ai.da.mapper.primary.entity.Affiliate; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AffiliateVO extends Affiliate { + + private Long linkViewCount; + + private String username; + +} diff --git a/src/main/java/com/ai/da/model/vo/BindEmailVO.java b/src/main/java/com/ai/da/model/vo/BindEmailVO.java new file mode 100644 index 00000000..e2d0509b --- /dev/null +++ b/src/main/java/com/ai/da/model/vo/BindEmailVO.java @@ -0,0 +1,9 @@ +package com.ai.da.model.vo; + +import lombok.Data; + +@Data +public class BindEmailVO { + + private String token; +} diff --git a/src/main/java/com/ai/da/model/vo/DesignCollectionItemVO.java b/src/main/java/com/ai/da/model/vo/DesignCollectionItemVO.java index dc15cd14..1c98e059 100644 --- a/src/main/java/com/ai/da/model/vo/DesignCollectionItemVO.java +++ b/src/main/java/com/ai/da/model/vo/DesignCollectionItemVO.java @@ -22,6 +22,8 @@ public class DesignCollectionItemVO { @ApiModelProperty("t_design_python_outfit id") private String designOutfitUrl; + private String objectSign; + public DesignCollectionItemVO() { } diff --git a/src/main/java/com/ai/da/model/vo/DesignCollectionVO.java b/src/main/java/com/ai/da/model/vo/DesignCollectionVO.java index 8ad6e7a4..807f364c 100644 --- a/src/main/java/com/ai/da/model/vo/DesignCollectionVO.java +++ b/src/main/java/com/ai/da/model/vo/DesignCollectionVO.java @@ -23,6 +23,8 @@ public class DesignCollectionVO { private String processId; + private List UnfinishedList; + public DesignCollectionVO() { } } diff --git a/src/main/java/com/ai/da/model/vo/DesignLikeVO.java b/src/main/java/com/ai/da/model/vo/DesignLikeVO.java index 2a38f8bd..a8d0c5d8 100644 --- a/src/main/java/com/ai/da/model/vo/DesignLikeVO.java +++ b/src/main/java/com/ai/da/model/vo/DesignLikeVO.java @@ -12,6 +12,8 @@ import java.util.List; @ApiModel("design like-响应") public class DesignLikeVO { + private Long userLikeSortId; + @ApiModelProperty("分组id") private Long userGroupId; @ApiModelProperty("分组详情id") @@ -19,6 +21,10 @@ public class DesignLikeVO { private String pictureName; + private Long userLikeId; + + private Integer sort; + public DesignLikeVO() { } } diff --git a/src/main/java/com/ai/da/model/vo/OrderListVO.java b/src/main/java/com/ai/da/model/vo/OrderListVO.java new file mode 100644 index 00000000..44e42bff --- /dev/null +++ b/src/main/java/com/ai/da/model/vo/OrderListVO.java @@ -0,0 +1,29 @@ +package com.ai.da.model.vo; + + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 用于订单列表展示(展示的是所有支付信息) + */ +@Data +public class OrderListVO { + + private Long id; + + private Float amount; + + private String paymentMethod; + + private String state; + + private String orderType; + + private String invoiceLink; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8") + private LocalDateTime createTime; +} diff --git a/src/main/java/com/ai/da/model/vo/PaymentInfoVO.java b/src/main/java/com/ai/da/model/vo/PaymentInfoVO.java new file mode 100644 index 00000000..caa1955c --- /dev/null +++ b/src/main/java/com/ai/da/model/vo/PaymentInfoVO.java @@ -0,0 +1,34 @@ +package com.ai.da.model.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@Data +@ApiModel("交易记录详情") +public class PaymentInfoVO { + private Long id; + @ApiModelProperty("付款用户名") + private String payer; + @ApiModelProperty("选择的支付平台 PayPal || Stripe || Alipay-HK") + private String platform; + @ApiModelProperty("支付的金额 单位:HKD") + private String payerTotal; + @ApiModelProperty("商品种类 new || renewal || credits") + private String type; + @ApiModelProperty("交易状态 Success || Fail || Pending") + private String status; + @ApiModelProperty("付款人所在国家") + private String country; + @ApiModelProperty("付款人所在城市") + private String city; + @ApiModelProperty("使用Stripe具体的支付方式") + private String paymentMethod; + @ApiModelProperty("信用卡支付的卡号后四位") + private String last4; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8") + private String createTime; +} diff --git a/src/main/java/com/ai/da/model/vo/UserLikeVO.java b/src/main/java/com/ai/da/model/vo/UserLikeVO.java index 589e8fc6..db4d4638 100644 --- a/src/main/java/com/ai/da/model/vo/UserLikeVO.java +++ b/src/main/java/com/ai/da/model/vo/UserLikeVO.java @@ -23,4 +23,7 @@ public class UserLikeVO { @ApiModelProperty("图片路径") private String designOutfitUrl; private String pictureName; + + private Integer sort; + private Long userLikeSortId; } diff --git a/src/main/java/com/ai/da/model/vo/ValidateElementVO.java b/src/main/java/com/ai/da/model/vo/ValidateElementVO.java index f2cb87a1..73912616 100644 --- a/src/main/java/com/ai/da/model/vo/ValidateElementVO.java +++ b/src/main/java/com/ai/da/model/vo/ValidateElementVO.java @@ -47,4 +47,8 @@ public class ValidateElementVO { private String modelSex; private String style; + + private List requestIdList; + + private Integer designNum; } diff --git a/src/main/java/com/ai/da/python/PythonService.java b/src/main/java/com/ai/da/python/PythonService.java index 08113d91..e3bebcd7 100644 --- a/src/main/java/com/ai/da/python/PythonService.java +++ b/src/main/java/com/ai/da/python/PythonService.java @@ -41,6 +41,7 @@ import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import java.io.File; +import java.io.FileWriter; import java.io.IOException; import java.math.BigDecimal; import java.math.RoundingMode; @@ -230,13 +231,17 @@ public class PythonService { designPythonObjects.setProcess_id(processId); long pinPrintNum = calculateDesignPinPrintNum(elementVO.getPrintBoardElements()); - long noPinPrintNum = calculateDesignNoPinPrintNum(elementVO.getPrintBoardElements()); - long noPrintNum = 8 - pinPrintNum - noPinPrintNum; + long noPinPrintNum = calculateDesignNoPinPrintNum(elementVO.getPrintBoardElements(), elementVO.getDesignNum()); + long noPrintNum = elementVO.getDesignNum() - pinPrintNum - noPinPrintNum; elementVO.setNoPinPrintNum(noPinPrintNum); int[] sketchNumbers = new int[3]; - for (int i = 0; i < 8; i++) { + int designNum = elementVO.getDesignNum(); + Set assembledObjects = new HashSet<>(); // 用于存储已组装的 DesignPythonObject + DesignPythonObject lastAssembledObject = null; // 上一次组装的对象 + + for (int i = 0; i < designNum; i++) { CurrentDesignPictureTypeEnum designPictureType = calculateCurrentDesignPictureTypeNew(elementVO, sketchNumbers, systemScale); if (designPictureType == null) break; @@ -255,19 +260,70 @@ public class PythonService { noPrintNum--; break; } -// updatePrintNumbers(designPrintPictureType, pinPrintNum, noPinPrintNum, noPrintNum); - DesignPythonItemPrint designPythonItemPrint = getRandomPrint(elementVO, designPrintPictureType); elementVO.setDesignPythonItemPrint(designPythonItemPrint); elementVO.setDesignPrintPictureTypeLayoutList(calculateCurrentDesignPintPictureTypeLayout(elementVO.getModelSex())); + List beforeAssemblyHasUseMd5List = new ArrayList<>(elementVO.getHasUseMd5List()); + DesignPythonObject pythonObject = createDesignPythonObject(elementVO, designPictureType, systemScale, singleOverall, switchCategory, i); + + // 如果当前对象与已组装的对象重复,则跳过当前组装 + DesignPythonObject designPythonObjectCopy = getCopy(pythonObject); + + boolean isDuplicate = assembledObjects.contains(designPythonObjectCopy); + + if (isDuplicate) { +// if (lastAssembledObject != null && assembledObjects.contains(lastAssembledObject)) { +// // 如果当前组装与前一个组装的对象重复,且前一个组装也重复,结束组装 +// System.out.println("当前组装的对象与前两个组装的对象重复,结束组装。"); +// break; +// } + elementVO.setHasUseMd5List(beforeAssemblyHasUseMd5List); + i --; + + switch (designPrintPictureType) { + case PIN: + pinPrintNum++; + break; + case NO_PIN: + noPinPrintNum++; + break; + case NO: + noPrintNum++; + break; + } + continue; + } + + // 将当前对象添加到已组装的集合中,并记录 + assembledObjects.add(designPythonObjectCopy); +// lastAssembledObject = designPythonObjectCopy; // 更新上一次组装的对象 - DesignPythonObject pythonObject = createDesignPythonObject(elementVO, designPictureType, systemScale, singleOverall, switchCategory); objects.add(pythonObject); redisUtil.addProcessId(processId, i + 1); } return designPythonObjects; } + private DesignPythonObject getCopy(DesignPythonObject pythonObject) { + DesignPythonObject designPythonObjectCopy = CopyUtil.copyObject(pythonObject, DesignPythonObject.class); + designPythonObjectCopy.setObjectSign(null); + DesignPythonBasic basic = designPythonObjectCopy.getBasic(); + basic.setSave_name(null); + designPythonObjectCopy.setBasic(basic); + List items = designPythonObjectCopy.getItems(); + List itemsCopy = new ArrayList<>(); + for (DesignPythonItem item : items) { + item.setElementId(null); + item.setIcon(null); + item.setBusinessId(null); + item.setImage_id(null); + item.setImageId(null); + itemsCopy.add(item); + } + designPythonObjectCopy.setItems(itemsCopy); + return designPythonObjectCopy; + } + private void updateSketchNumbers(CurrentDesignPictureTypeEnum designPictureType, int[] sketchNumbers) { switch (designPictureType) { case PIN: @@ -296,22 +352,26 @@ public class PythonService { } } - private DesignPythonObject createDesignPythonObject(ValidateElementVO elementVO, CurrentDesignPictureTypeEnum designPictureType, BigDecimal systemScale, String singleOverall, String switchCategory) { + private DesignPythonObject createDesignPythonObject(ValidateElementVO elementVO, CurrentDesignPictureTypeEnum designPictureType, BigDecimal systemScale, String singleOverall, String switchCategory, int i) { DesignPythonObject pythonObject = new DesignPythonObject(); pythonObject.setItems(coverToDesignPythonItemNew(elementVO, designPictureType, systemScale)); pythonObject.setBasic(coverToBasic(pythonObject.getItems().get(0), singleOverall, switchCategory, elementVO.getDesignLibraryModelPoint())); + if (CollectionUtil.isNotEmpty(elementVO.getRequestIdList())) { + pythonObject.setObjectSign(elementVO.getRequestIdList().get(i)); + } return pythonObject; } private CurrentDesignPictureTypeEnum calculateCurrentDesignPictureTypeNew(ValidateElementVO elementVO, int[] sketchNumbers, BigDecimal systemScale) { List pinData = getPinData(elementVO); + Integer designNum = elementVO.getDesignNum(); if (CollectionUtil.isNotEmpty(pinData)) { return CurrentDesignPictureTypeEnum.PIN; } else { if (sketchNumbers[1] == 0 && sketchNumbers[2] == 0) { - sketchNumbers[1] = systemScale.multiply(BigDecimal.valueOf(8 - sketchNumbers[0])).setScale(0, BigDecimal.ROUND_HALF_UP).intValue(); - sketchNumbers[2] = 8 - sketchNumbers[0] - sketchNumbers[1]; + sketchNumbers[1] = systemScale.multiply(BigDecimal.valueOf(designNum - sketchNumbers[0])).setScale(0, BigDecimal.ROUND_HALF_UP).intValue(); + sketchNumbers[2] = designNum - sketchNumbers[0] - sketchNumbers[1]; } if (sketchNumbers[2] > 0 && sketchNumbers[1] > 0) { Long l = RandomsUtil.randomSysFile(0l, 2l); @@ -491,7 +551,7 @@ public class PythonService { } //计算print 非Pin图片剩余张数 - private long calculateDesignNoPinPrintNum(List printBoardElements) { + private long calculateDesignNoPinPrintNum(List printBoardElements, Integer designNum) { if (CollectionUtils.isEmpty(printBoardElements)) { return 0; } @@ -500,10 +560,10 @@ public class PythonService { return 0; } else { long pinNum = printBoardElements.stream().filter(f -> f.getHasPin() == 1).count(); - if (8 - pinNum < 4) { - return RandomsUtil.randomSysFile(0L, 8 - pinNum + 1); + if (designNum - pinNum < designNum/2) { + return RandomsUtil.randomSysFile(0L, designNum - pinNum + 1); } else { - return RandomsUtil.randomSysFile(0L, 4L + 1); + return RandomsUtil.randomSysFile(0L, (long) (designNum/2 + 1)); } } } @@ -553,7 +613,7 @@ public class PythonService { if (elementVO.getSingleOverall().equals(SingleOverallEnum.OVERALL.getRealName())) { List otherSketchCategoryList = getOtherSketchCategoryList(elementVO.getModelSex(), designPythonItem); if (!otherSketchCategoryList.isEmpty()) { - JSONObject attributeRecognition = getAttributeRecognition(designPythonItem, designPythonItem.getType(), elementVO.getModelSex()); + JSONObject attributeRecognition = getAttributeRecognition(designPythonItem.getPath(), designPythonItem.getType(), elementVO.getModelSex()); for (String styleCategory : otherSketchCategoryList) { DesignPythonItem otherSketch = processAttributeRecognition(attributeRecognition, elementVO, designPictureType, styleCategory, systemScale); itemList.add(otherSketch); @@ -790,7 +850,7 @@ public class PythonService { return attributeRetrieval; } - public JSONObject getAttributeRecognition(DesignPythonItem designPythonItem, String styleCategory, String modelSex) { + public JSONObject getAttributeRecognition(String sketchImgUrl, String styleCategory, String modelSex) { OkHttpClient client = new OkHttpClient().newBuilder() .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS) @@ -801,7 +861,7 @@ public class PythonService { JSONObject paramJSONObject = new JSONObject(); paramJSONObject.put("category", styleCategory); paramJSONObject.put("colony", modelSex); - paramJSONObject.put("sketch_img_url", designPythonItem.getPath()); + paramJSONObject.put("sketch_img_url", sketchImgUrl); JSONArray paramArray = new JSONArray(); paramArray.add(paramJSONObject); String param = JSON.toJSONString(paramArray, SerializerFeature.DisableCircularReferenceDetect); @@ -3003,6 +3063,58 @@ public class PythonService { throw new BusinessException("design.interface.exception"); } + public JSONObject designStream(DesignPythonObjects designPythonObjects) { + // todo 限流校验 +// AccessLimitUtils.validate("design",5); + 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"); + //关闭FastJson的引用检测 防止出现$ref 现象 + String param = JSON.toJSONString(designPythonObjects, SerializerFeature.DisableCircularReferenceDetect); + log.info("design请求python 参数:####{}", param); + RequestBody body = RequestBody.create(mediaType, param); + Request request = new Request.Builder() + .url(accessPythonIp + ":" + accessPythonPort + "/api/design_v2") +// .url(fastApiPythonAddress + "/api/design") +// .url(accessPythonIp + ":10200/aifda/api/v1.0/generate") + .method("POST", body) + .addHeader("Authorization", "Basic YWlkbGFiOjEyMw==") + .addHeader("Content-Type", "application/json") + .build(); + Response response; + String responseBody; + try { + response = client.newCall(request).execute(); + } catch (IOException ioException) { + AccessLimitUtils.validateOut("design"); + log.error("PythonService##design异常###{}", ExceptionUtil.getThrowableList(ioException)); + throw new BusinessException("design.interface.exception"); + } + //去除限流 +// AccessLimitUtils.validateOut("design"); + if (response.isSuccessful()) { + try { + if (Objects.nonNull(response.body())) { + responseBody = response.body().string(); + JSONObject responseObject = JSON.parseObject(responseBody); + log.info("PythonService##responseObject###{}", responseObject); + return responseObject; + } + throw new BusinessException("design.interface.exception"); + } catch (IOException | JSONException e) { + log.error("PythonService##design异常###{}", e.getMessage()); + throw new BusinessException("design.interface.exception"); + } + } + log.error("PythonService##design异常response###{}", response); + //生成失败 + throw new BusinessException("design.interface.exception"); + } + /** * 暂时未用 */ @@ -3566,4 +3678,121 @@ public class PythonService { //生成失败 throw new BusinessException("bright.interface.exception"); } + + public JSONObject attributeRecognition(List pictureUrls,List ids, List category) { + //限流校验 + AccessLimitUtils.validate("attributeRecognition", 20); + OkHttpClient client = new OkHttpClient().newBuilder() + .connectTimeout(30, TimeUnit.SECONDS) + .pingInterval(5, TimeUnit.SECONDS)//websocket轮训间隔(单位:秒) + .readTimeout(300, TimeUnit.SECONDS)//读取超时(单位:秒) + .writeTimeout(300, TimeUnit.SECONDS)//写入超时(单位:秒) + .build(); + MediaType mediaType = MediaType.parse("application/json"); + Map> content = Maps.newHashMap(); + //识别图片路径数组 + content.put("upload_img_path", pictureUrls); + //识别图片id数组 + content.put("upload_img_id", ids); + content.put("upload_img_category", category); + RequestBody body = RequestBody.create(mediaType, JSON.toJSONString(content)); + Request request = new Request.Builder() + .url(accessPythonIp + ":9993/api/attribute") + .method("POST", body) + .addHeader("Authorization", "Basic YWlkbGFiOjEyMw==") + .addHeader("Content-Type", "application/json") + .build(); + Response response = null; + String bodyStr = null; + try { + log.info("识别python对应的属性标签值请求入参content###{}", JSON.toJSONString(content)); + response = client.newCall(request).execute(); + bodyStr = response.body().string(); + } catch (IOException ioException) { + log.error("PythonService###attributeRecognition异常##{}", ExceptionUtil.getThrowableList(ioException)); + } + log.info("识别python对应的属性标签值结果###{}",bodyStr.trim()); + //去除限流 + AccessLimitUtils.validateOut("attributeRecognition"); + if (Objects.isNull(response)) { + log.error("PythonService##attributeRecognition异常###{}", "response or body is empty!"); + throw new BusinessException("attribute recognition exception!"); + } + JSONObject jsonObject = JSON.parseObject(JSON.toJSONString(response)); + Boolean result = jsonObject.getBoolean("successful"); + if (result) { + JSONObject attributeJSONObject = JSON.parseObject(bodyStr.trim()); + return attributeJSONObject; + } + log.info("识别python对应的属性标签值异常###{}", jsonObject); + //生成失败 + throw new BusinessException("Atribute recognition exception!"); + } + + public String designBatch(DesignPythonObjects designPythonObjects, Long accountId, int designNum, String taskId) { + // todo 限流校验 + // AccessLimitUtils.validate("design",5); + + // 将 designPythonObjects 写入文件 + File file = new File("design_batch_test.txt"); + try (FileWriter writer = new FileWriter(file)) { + String param = JSON.toJSONString(designPythonObjects, SerializerFeature.DisableCircularReferenceDetect); + writer.write(param); + log.info("设计请求参数已写入文件:####{}", file.getAbsolutePath()); + } catch (IOException e) { + log.error("写入文件异常:{}", e.getMessage()); + throw new BusinessException("file.write.exception"); + } + + OkHttpClient client = new OkHttpClient().newBuilder() + .connectTimeout(30, TimeUnit.SECONDS) + .pingInterval(5, TimeUnit.SECONDS) + .readTimeout(60, TimeUnit.SECONDS) + .writeTimeout(60, TimeUnit.SECONDS) + .build(); + + // 构建 multipart 表单 + RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM) + .addFormDataPart("file", "design_batch_test.txt", + RequestBody.create(MediaType.parse("text/plain"), file)) + .addFormDataPart("tasks_id", taskId) + .addFormDataPart("user_id", String.valueOf(accountId)) + .addFormDataPart("file_name", "design_batch_test" + taskId + ".json") + .addFormDataPart("total", String.valueOf(designNum)) + .build(); + + Request request = new Request.Builder() + .url("http://18.167.251.121:9994/api/design_batch_generate") + .method("POST", body) + .addHeader("Content-Type", "multipart/form-data") + .build(); + + Response response; + String responseBody; + try { + response = client.newCall(request).execute(); + } catch (IOException ioException) { + AccessLimitUtils.validateOut("design"); + log.error("PythonService##design异常###{}", ExceptionUtil.getThrowableList(ioException)); + throw new BusinessException("design.interface.exception"); + } + + if (response.isSuccessful()) { + try { + if (Objects.nonNull(response.body())) { + responseBody = response.body().string(); + JSONObject responseObject = JSON.parseObject(responseBody); + log.info("PythonService##responseObject###{}", responseObject); + return taskId; + } + throw new BusinessException("design.interface.exception"); + } catch (IOException | JSONException e) { + log.error("PythonService##design异常###{}", e.getMessage()); + throw new BusinessException("design.interface.exception"); + } + } + log.error("PythonService##design异常response###{}", response); + throw new BusinessException("design.interface.exception"); + } + } diff --git a/src/main/java/com/ai/da/python/vo/DesignPythonObject.java b/src/main/java/com/ai/da/python/vo/DesignPythonObject.java index aa6a11e2..12946423 100644 --- a/src/main/java/com/ai/da/python/vo/DesignPythonObject.java +++ b/src/main/java/com/ai/da/python/vo/DesignPythonObject.java @@ -15,4 +15,6 @@ public class DesignPythonObject { * basic 选项 */ DesignPythonBasic basic; + + private String objectSign; } diff --git a/src/main/java/com/ai/da/python/vo/DesignPythonObjects.java b/src/main/java/com/ai/da/python/vo/DesignPythonObjects.java index af5fa3be..1f61ee07 100644 --- a/src/main/java/com/ai/da/python/vo/DesignPythonObjects.java +++ b/src/main/java/com/ai/da/python/vo/DesignPythonObjects.java @@ -16,4 +16,6 @@ public class DesignPythonObjects { * design新增的library */ List addLibrary; + + private String requestId; } diff --git a/src/main/java/com/ai/da/service/AccountService.java b/src/main/java/com/ai/da/service/AccountService.java index ab6320d0..531c3fc7 100644 --- a/src/main/java/com/ai/da/service/AccountService.java +++ b/src/main/java/com/ai/da/service/AccountService.java @@ -1,10 +1,13 @@ package com.ai.da.service; +import com.ai.da.common.response.PageBaseResponse; import com.ai.da.mapper.primary.entity.Account; +import com.ai.da.mapper.primary.entity.AccountExtend; import com.ai.da.mapper.primary.entity.TrialOrder; import com.ai.da.model.dto.*; import com.ai.da.model.vo.AccountLoginVO; import com.ai.da.model.vo.AccountPreLoginVO; +import com.ai.da.model.vo.BindEmailVO; import com.ai.da.model.vo.PersonalHomepageVO; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; @@ -45,7 +48,9 @@ public interface AccountService extends IService { * @param accountBindEmailDTO * @return */ - Boolean bindEmail(AccountBindEmailDTO accountBindEmailDTO); + BindEmailVO bindEmail(AccountBindEmailDTO accountBindEmailDTO, HttpServletRequest request); + + BindEmailVO bindEmail(String email); /** * 忘记密码 @@ -169,15 +174,15 @@ public interface AccountService extends IService { void registerUserToVisitor(); - Map getNicknameModifyTimes(); + Long getNicknameModifyTimes(); void editUserName(String newUserName); - void verifyUserEmail(String verifyCode); + /*void verifyUserEmail(String verifyCode); void changeUserEmail(String newMailbox); - void activateNewEmail(String token); + void activateNewEmail(String token);*/ String updateNoLoginRequiredNew(NoLoginRequiredDTO noLoginRequiredDTO, HttpServletRequest request); @@ -188,4 +193,36 @@ public interface AccountService extends IService { List getPaidCustomerEmail(); void temporaryUpgrade(); + + AccountLoginVO enterpriseLogin(AccountLoginDTO accountDTO); + + AccountLoginVO schoolLogin(AccountLoginDTO accountDTO); + + Boolean addSubAccount(AddSubAccountDTO addSubAccountDTO); + + Boolean deleteSubAccount(AddSubAccountDTO addSubAccountDTO); + + PageBaseResponse subAccountList(SubAccountPageDTO subAccountPageDTO); + + Account accountDetail(Long id); + + AccountLoginVO parseGoogleCredential(String credential, Integer type); + + AccountLoginVO parseWeChatCode(String code, Integer type); + + AccountLoginVO getAccountDetail(); + + AccountExtend bindGoogle(String credential); + + AccountExtend bindWeChat(String code); + + Boolean unbindWeChat(); + + Boolean unbindGoogle(); + + void updateAccountValidity(Long accountId, Long currentPeriodEnd); + + void updateUserRoleAndCredits(Long accountId, String type); + + void updateUserInfo(String country, String occupation); } diff --git a/src/main/java/com/ai/da/service/AffiliateService.java b/src/main/java/com/ai/da/service/AffiliateService.java new file mode 100644 index 00000000..fa03936f --- /dev/null +++ b/src/main/java/com/ai/da/service/AffiliateService.java @@ -0,0 +1,32 @@ +package com.ai.da.service; + +import com.ai.da.common.response.PageBaseResponse; +import com.ai.da.mapper.primary.entity.Affiliate; +import com.ai.da.model.dto.AffiliateQueryDTO; +import com.ai.da.model.vo.AffiliateInvitationDetailsVO; +import com.ai.da.model.vo.AffiliateVO; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; + +public interface AffiliateService extends IService { + + Boolean registerAsAnAffiliate(String promotionMethod); + + PageBaseResponse getAffiliateList(AffiliateQueryDTO affiliateQueryDTO); + + AffiliateVO personalAffiliateCenter(); + + double[] getPersonalMonthlyIncome(int year); + + Boolean applicationApproval(Long id, Boolean isApproved); + + void updateAffiliateInfoWithPayment(); + + Boolean affiliateLinkViewsIncrease(Long id); + + IPage getEachAffiliateGeneratedRevenue(AffiliateQueryDTO affiliateQueryDTO); + + Affiliate getByAccountId(Long accountId); + + void commissionCalculation(Integer year, Integer month); +} diff --git a/src/main/java/com/ai/da/service/AliPayService.java b/src/main/java/com/ai/da/service/AliPayService.java index 66dd334d..450a14bb 100644 --- a/src/main/java/com/ai/da/service/AliPayService.java +++ b/src/main/java/com/ai/da/service/AliPayService.java @@ -1,9 +1,12 @@ package com.ai.da.service; +import com.ai.da.model.dto.ProductPurchaseDTO; + +import javax.servlet.http.HttpServletRequest; import java.util.Map; public interface AliPayService { - String tradeCreate(Integer amount,String returnUrl); + String tradeCreate(ProductPurchaseDTO productPurchaseDTO, HttpServletRequest request); String tradeNotify(Map params); diff --git a/src/main/java/com/ai/da/service/AlipayHKService.java b/src/main/java/com/ai/da/service/AlipayHKService.java index 13495fe2..a8492c0b 100644 --- a/src/main/java/com/ai/da/service/AlipayHKService.java +++ b/src/main/java/com/ai/da/service/AlipayHKService.java @@ -1,10 +1,13 @@ package com.ai.da.service; import com.ai.da.model.dto.AlipayHKCallbackDTO; +import com.ai.da.model.dto.ProductPurchaseDTO; + +import javax.servlet.http.HttpServletRequest; public interface AlipayHKService { - String createOrder(Integer amount, String wallet); + String createOrder(ProductPurchaseDTO productPurchaseDTO, HttpServletRequest request); String callback(String paramString); diff --git a/src/main/java/com/ai/da/service/ConvenientInquiryService.java b/src/main/java/com/ai/da/service/ConvenientInquiryService.java index fa4210f4..fa279d0d 100644 --- a/src/main/java/com/ai/da/service/ConvenientInquiryService.java +++ b/src/main/java/com/ai/da/service/ConvenientInquiryService.java @@ -1,15 +1,19 @@ package com.ai.da.service; +import com.ai.da.common.response.PageBaseResponse; import com.ai.da.mapper.primary.entity.Account; import com.ai.da.mapper.primary.entity.Questionnaire; import com.ai.da.mapper.primary.entity.TrialOrder; import com.ai.da.model.dto.AccountAddDTO; +import com.ai.da.model.dto.QueryPaymentInfoDTO; +import com.ai.da.model.vo.PaymentInfoVO; import com.ai.da.model.vo.QuestionnaireFeedbackVO; import com.ai.da.model.vo.QuestionnaireVO; import com.ai.da.model.vo.QueryUserConditionsVO; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; +import javax.servlet.http.HttpServletResponse; import java.util.List; import java.util.Map; @@ -43,4 +47,10 @@ public interface ConvenientInquiryService extends IService { IPage getUserInfo(QueryUserConditionsVO queryUserConditionsVO); List> getAllUserIdList(); + + PageBaseResponse queryTransactionRecords(QueryPaymentInfoDTO queryPaymentInfoDTO); + + Map> getCities(); + + String exportTransactionRecords(QueryPaymentInfoDTO queryPaymentInfoDTO, HttpServletResponse response); } diff --git a/src/main/java/com/ai/da/service/CreditsService.java b/src/main/java/com/ai/da/service/CreditsService.java index 1d076527..62d9e5e4 100644 --- a/src/main/java/com/ai/da/service/CreditsService.java +++ b/src/main/java/com/ai/da/service/CreditsService.java @@ -17,9 +17,9 @@ public interface CreditsService extends IService { String getCredits(Long accountId); - void creditsRefund(Long accountId, Integer quantity); + void creditsRefund(Long accountId, Integer quantity, String orderNo); - void insertToCreditsDetail(Long accountId, String changeEvent, String credits, String changeType); + void insertToCreditsDetail(Long accountId, String changeEvent, String credits, String changeType, String orderNo); PageBaseResponse queryCreditsDetailsPage(QueryIncomeOrExpenditureDTO queryPageByTimeDTO); diff --git a/src/main/java/com/ai/da/service/DesignService.java b/src/main/java/com/ai/da/service/DesignService.java index b346c8bf..9dc7591d 100644 --- a/src/main/java/com/ai/da/service/DesignService.java +++ b/src/main/java/com/ai/da/service/DesignService.java @@ -7,10 +7,13 @@ import com.ai.da.model.vo.DesignCollectionVO; import com.ai.da.model.vo.DesignItemDetailVO; import com.ai.da.model.vo.DesignLikeVO; import com.ai.da.python.vo.DesignPythonObjects; +import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.extension.service.IService; import java.math.BigDecimal; import java.util.List; +import java.util.Map; +import java.util.Objects; /** * 服务类 @@ -25,7 +28,7 @@ public interface DesignService extends IService { * @param designDTO * @return */ - DesignCollectionVO designCollection(DesignCollectionDTO designDTO); + String designCollection(DesignCollectionDTO designDTO); /** * redesign @@ -33,7 +36,7 @@ public interface DesignService extends IService { * @param reDesignDTO * @return */ - DesignCollectionVO reDesignCollection(ReDesignCollectionDTO reDesignDTO); + String reDesignCollection(ReDesignCollectionDTO reDesignDTO); /** * redesign @@ -99,4 +102,14 @@ public interface DesignService extends IService { List getModel(List designItemIdList); Long getCountByUserAndTime(String startTime, String endTime, List accountIds); + + Boolean receiveDesignResults(JSONObject responseObject); + + DesignCollectionVO getDesignResult(String requestId, List objectSignList); + + String designCloud(DesignCollectionDTO designDTO); + + void processDesignBatch(Map designBatchResult); + + Boolean sort(UserLikeSortDTO userLikeSortDTO); } diff --git a/src/main/java/com/ai/da/service/OrderInfoService.java b/src/main/java/com/ai/da/service/OrderInfoService.java index 35da4881..19264239 100644 --- a/src/main/java/com/ai/da/service/OrderInfoService.java +++ b/src/main/java/com/ai/da/service/OrderInfoService.java @@ -2,16 +2,20 @@ package com.ai.da.service; import com.ai.da.common.enums.OrderStatusEnum; +import com.ai.da.common.enums.ProductEnum; 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 javax.servlet.http.HttpServletRequest; import java.util.List; public interface OrderInfoService extends IService { - OrderInfo createOrderByProductId(Integer productId, String paymentType); + OrderInfo createOrderByProductId(Integer productId, String paymentType, HttpServletRequest request); + + OrderInfo createOrderByProductId(Integer amount, String paymentType, ProductEnum product, HttpServletRequest request); void saveCodeUrl(String orderNo, String codeUrl); @@ -28,4 +32,7 @@ public interface OrderInfoService extends IService { PageBaseResponse getOrderByPage(QueryPageByTimeDTO queryPageByTimeDTO); void updateOrderNoById(Long id, String orderNo); + + void updateTotalFeeByOrderNo(String orderNo); + } diff --git a/src/main/java/com/ai/da/service/PayPalCheckoutService.java b/src/main/java/com/ai/da/service/PayPalCheckoutService.java index d9da17e4..e6256827 100644 --- a/src/main/java/com/ai/da/service/PayPalCheckoutService.java +++ b/src/main/java/com/ai/da/service/PayPalCheckoutService.java @@ -1,5 +1,6 @@ package com.ai.da.service; +import com.ai.da.model.dto.ProductPurchaseDTO; import com.paypal.http.exceptions.SerializeException; import com.paypal.orders.Order; @@ -8,11 +9,10 @@ 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; + HashMap createOrder(ProductPurchaseDTO productPurchaseDTO, HttpServletRequest request) throws SerializeException; // String callback(@SuppressWarnings("rawtypes") Map map); diff --git a/src/main/java/com/ai/da/service/PaymentInfoService.java b/src/main/java/com/ai/da/service/PaymentInfoService.java index 74492683..03db46a4 100644 --- a/src/main/java/com/ai/da/service/PaymentInfoService.java +++ b/src/main/java/com/ai/da/service/PaymentInfoService.java @@ -1,25 +1,35 @@ package com.ai.da.service; +import com.ai.da.common.response.PageBaseResponse; import com.ai.da.mapper.primary.entity.PaymentInfo; import com.ai.da.model.dto.AlipayHKCallbackDTO; +import com.ai.da.model.dto.QueryPageByTimeDTO; +import com.ai.da.model.vo.OrderListVO; +import com.baomidou.mybatisplus.extension.service.IService; import com.paypal.orders.Order; -import com.stripe.model.checkout.Session; +import com.stripe.model.Charge; +import com.stripe.model.Invoice; +import java.util.List; import java.util.Map; -public interface PaymentInfoService { +public interface PaymentInfoService extends IService { void createPaymentInfo(String plainText); - void createPaymentInfoForAliPay(Map params); + void createPaymentInfoForAliPay(Map params, String type); - void createPaymentInfoForPayPal(Order order); + void createPaymentInfoForPayPal(Order order, String type); - void createPaymentInfoForAliPayHK(AlipayHKCallbackDTO alipayHKCallbackDTO); + void createPaymentInfoForAliPayHK(AlipayHKCallbackDTO alipayHKCallbackDTO, String type); - void createPaymentInfoForStripe(Session session); + PaymentInfo createOrUpdatePaymentInfoForStripe(Invoice invoice); - PaymentInfo getPaymentInfoByOrderId(String orderId); + PaymentInfo createOrUpdatePaymentInfoForStripe(Charge charge); + + List getPaymentInfoByOrderNo(String orderId, String order); void updatePaymentStatusById(Long id, String status, String content); + + PageBaseResponse getPaymentInfo(QueryPageByTimeDTO queryPageByTimeDTO); } diff --git a/src/main/java/com/ai/da/service/StripeService.java b/src/main/java/com/ai/da/service/StripeService.java index 98709597..008b615c 100644 --- a/src/main/java/com/ai/da/service/StripeService.java +++ b/src/main/java/com/ai/da/service/StripeService.java @@ -1,15 +1,52 @@ package com.ai.da.service; +import com.ai.da.mapper.primary.entity.SubscriptionInfo; +import com.ai.da.model.dto.ProductPurchaseDTO; +import com.stripe.exception.StripeException; + import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Map; public interface StripeService { - String pay(Integer quantity, String returnUrl); + String pay(ProductPurchaseDTO productPurchaseDTO, HttpServletRequest request); Boolean notify(HttpServletRequest request); + SubscriptionInfo getLatestSubscriptionInfoByAccountId(Long accountId); + String refund(String amount, String orderId, String reason); void checkOrderStatus(String orderNo); + + List getSubscriptionIds(String name, String userEmail) throws StripeException; + + Map getPaymentMethodByInvoiceId(String invoiceId); + + void cancelSubscription(String orderNo, String cancelReason); + + void cancelSubscriptionTemp(String subscriptionId); + + Map getPaymentMethod(String paymentMethodId); + + /*void updateSubscription(String subscriptionId); + + void resume(String subscriptionId);*/ + + void subscriptionReminder(); + + void checkSubscriptionExpiration(); + + String createSubscriptionTemp(String name, String email); + + String changeCustomerPayment(String name, String email); + + boolean sendRenewalFailEmail(String invoiceId, String subscriptionId, String orderNo); + + List> getCustomerPaymentMethod(String name, String email); + + String detachCustomerAllPaymentMethod(String name, String email); + +// Map getIp(HttpServletRequest request); } diff --git a/src/main/java/com/ai/da/service/UserLikeGroupService.java b/src/main/java/com/ai/da/service/UserLikeGroupService.java index 918d29f5..552e9fe3 100644 --- a/src/main/java/com/ai/da/service/UserLikeGroupService.java +++ b/src/main/java/com/ai/da/service/UserLikeGroupService.java @@ -4,6 +4,7 @@ import com.ai.da.mapper.primary.entity.CanvasElementUpload; import com.ai.da.mapper.primary.entity.ToProductImageResult; import com.ai.da.mapper.primary.entity.UserLikeGroup; import com.ai.da.model.dto.ExportSaveDTO; +import com.ai.da.model.dto.ProductImageInitializeDTO; import com.ai.da.model.dto.ProductImageLikeDTO; import com.ai.da.model.dto.ToProductImageDTO; import com.ai.da.model.vo.*; @@ -64,4 +65,6 @@ public interface UserLikeGroupService extends IService { String likeHistoryRelSketch(); String download(); + + Boolean productImageInitialize(ProductImageInitializeDTO productImageInitializeDTO); } 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 27279068..f6bc567c 100644 --- a/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java @@ -7,9 +7,11 @@ import com.ai.da.common.context.UserContext; import com.ai.da.common.enums.AuthenticationOperationTypeEnum; import com.ai.da.common.enums.CreditsEventsEnum; import com.ai.da.common.enums.LoginTypeEnum; +import com.ai.da.common.response.PageBaseResponse; import com.ai.da.common.response.ResultEnum; import com.ai.da.common.security.jwt.JWTTokenHelper; import com.ai.da.common.utils.*; +import com.ai.da.mapper.primary.AccountExtendMapper; import com.ai.da.mapper.primary.AccountMapper; import com.ai.da.mapper.primary.QuestionnaireMapper; import com.ai.da.mapper.primary.TrialOrderMapper; @@ -20,11 +22,16 @@ import com.ai.da.model.enums.Language; import com.ai.da.model.vo.*; import com.ai.da.service.*; import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.jackson2.JacksonFactory; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import io.netty.util.internal.StringUtil; @@ -41,7 +48,10 @@ import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import javax.sql.DataSource; +import java.io.IOException; import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -97,6 +107,12 @@ public class AccountServiceImpl extends ServiceImpl impl @Resource private RedisUtil redisUtil; + @Resource + private StripeService stripeService; + + @Resource + private AffiliateService affiliateService; + @Override @Transactional(rollbackFor = Exception.class) public AccountPreLoginVO preLogin(AccountPreLoginDTO accountDTO) { @@ -223,6 +239,9 @@ public class AccountServiceImpl extends ServiceImpl impl private void validateUserValidaExpire(Account account) { Long currentTime = new Date().getTime(); + if (account.getSystemUser().equals(0)){ + return; + } if (Objects.nonNull(account.getValidStartTime())) { if (currentTime < account.getValidStartTime()) { throw new BusinessException("user.expired"); @@ -230,7 +249,9 @@ public class AccountServiceImpl extends ServiceImpl impl } if (Objects.nonNull(account.getValidEndTime())) { if (currentTime > account.getValidEndTime()) { - throw new BusinessException("user.expired"); + toVisitor(account); + return; +// throw new BusinessException("user.expired"); } } } @@ -267,14 +288,14 @@ public class AccountServiceImpl extends ServiceImpl impl } @Override - public Boolean bindEmail(AccountBindEmailDTO accountBindEmailDTO) { - Account account = baseMapper.selectById(accountBindEmailDTO.getUserId()); + public BindEmailVO bindEmail(AccountBindEmailDTO accountBindEmailDTO, HttpServletRequest request) { + AuthPrincipalVo userHolder = UserContext.getUserHolder(); + Account account = accountMapper.selectById(userHolder.getId()); + if (Objects.isNull(account)) { throw new BusinessException("userName.does.not.exist", ResultEnum.PROMPT.getCode()); } - if (StringUtils.isNotBlank(account.getUserEmail())) { - throw new BusinessException("user.has.bound.mailbox"); - } + //校验邮箱验证码 String verifyCode = LocalCacheUtils.getVerifyCodeCache(AuthenticationOperationTypeEnum.BIND_MAILBOX.name() + "_" + accountBindEmailDTO.getUserEmail()); if (StringUtils.isBlank(verifyCode)) { @@ -284,8 +305,46 @@ public class AccountServiceImpl extends ServiceImpl impl throw new BusinessException("verification.code.error", ResultEnum.PROMPT.getCode()); } //绑定 - updatePwdByUserId(accountBindEmailDTO.getUserEmail(), accountBindEmailDTO.getUserId()); - return Boolean.TRUE; + + BindEmailVO result = new BindEmailVO(); + + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(Account::getUserEmail, accountBindEmailDTO.getUserEmail()); + List accounts = accountMapper.selectList(qw); + if (CollectionUtil.isNotEmpty(accounts)) { + Account accountOld = accounts.get(0); + + QueryWrapper accountExtendQW = new QueryWrapper<>(); + accountExtendQW.lambda().eq(AccountExtend::getAccountId, userHolder.getId()); + accountExtendQW.lambda().eq(AccountExtend::getAuthType, "Wechat"); + AccountExtend accountExtend = accountExtendMapper.selectOne(accountExtendQW); + accountExtend.setAccountId(accountOld.getId()); + accountExtendMapper.updateById(accountExtend); + + accountMapper.deleteById(userHolder.getId()); + + String token = LocalCacheUtils.getTokenCache(String.valueOf(accountOld.getId())); + if (StringUtils.isNotBlank(token)) { + //用户已登入 + result.setToken(token); + } else { + result.setToken(createAccountToken(accountOld)); + } + return result; + } + Account accountNew = accountMapper.selectById(userHolder.getId()); + accountNew.setUserEmail(accountBindEmailDTO.getUserEmail()); + accountMapper.updateById(accountNew); + + TrialOrder trialOrder = CopyUtil.copyObject(accountNew, TrialOrder.class); + trialOrder.setCreateTime(LocalDateTime.now()); + trialOrder.setStatus(1); + // 获取用户申请试用IP + String ipAddress = RequestInfoUtil.getIpAddress(request); + trialOrder.setIp(ipAddress); + trialOrderMapper.insert(trialOrder); + + return result; } @Transactional(rollbackFor = Exception.class) @@ -367,7 +426,7 @@ public class AccountServiceImpl extends ServiceImpl impl if (Objects.isNull(authenticationOperationTypeEnum)) { throw new BusinessException("unknown.authentication.operation.type"); } - Account emailAccount = getOneByEmail(emailSendDTO.getEmail()); +// Account emailAccount = getOneByEmail(emailSendDTO.getEmail()); String randomVerifyCode = RandomsUtil.generateVerifyCode(100000L, 999999L); LocalCacheUtils.setVerifyCodeCache( emailSendDTO.getOperationType() + "_" + emailSendDTO.getEmail(), randomVerifyCode); @@ -386,13 +445,44 @@ public class AccountServiceImpl extends ServiceImpl impl SendEmailUtil.EXCEPTION_ID_TEMPLATE_ID, randomVerifyCode); break; case BIND_MAILBOX: + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(Account::getUserEmail, emailSendDTO.getEmail()); + List accounts = accountMapper.selectList(qw); + if (CollectionUtil.isNotEmpty(accounts)) { + Account account = accounts.get(0); + QueryWrapper accountExtendQW = new QueryWrapper<>(); + accountExtendQW.lambda().eq(AccountExtend::getAccountId, account.getId()); + accountExtendQW.lambda().eq(AccountExtend::getAuth, "WeChat"); + List accountExtends = accountExtendMapper.selectList(accountExtendQW); + if (CollectionUtil.isNotEmpty(accountExtends)) { + throw new BusinessException("This email account has already been linked to another WeChat account."); + } + } result = SendEmailUtil.send(emailSendDTO.getEmail(), null, SendEmailUtil.BIND_MAILBOX_TEMPLATE_ID, randomVerifyCode); + + if (!StringUtil.isNullOrEmpty(emailSendDTO.getCountry()) || !StringUtil.isNullOrEmpty(emailSendDTO.getOccupation())){ + Long accountId = UserContext.getUserHolder().getId(); + Account account = baseMapper.selectById(accountId); + account.setCountry(emailSendDTO.getCountry()); + account.setOccupation(emailSendDTO.getOccupation()); + baseMapper.updateById(account); + } break; case CHANGE_MAILBOX: result = SendEmailUtil.send(emailSendDTO.getEmail(), null, SendEmailUtil.CHANGE_MAILBOX_TEMPLATE_ID, randomVerifyCode); break; + case UPDATE_USERINFO: + if (!StringUtil.isNullOrEmpty(emailSendDTO.getCountry()) || !StringUtil.isNullOrEmpty(emailSendDTO.getOccupation())){ + Long accountId = UserContext.getUserHolder().getId(); + Account account = baseMapper.selectById(accountId); + account.setCountry(emailSendDTO.getCountry()); + account.setOccupation(emailSendDTO.getOccupation()); + baseMapper.updateById(account); + result = true; + } + break; default: } if (!result) { @@ -1205,6 +1295,7 @@ public class AccountServiceImpl extends ServiceImpl impl account.setValidStartTime(Instant.now().toEpochMilli()); account.setCreateDate(new Date()); account.setCredits(BigDecimal.valueOf(0)); + account.setInvitationCode(accountDesignWorksRegisterDTO.getInvitationCode()); accountMapper.insert(account); AccountLoginVO response = CopyUtil.copyObject(account, AccountLoginVO.class); response.setEmail(account.getUserEmail()); @@ -1421,6 +1512,8 @@ public class AccountServiceImpl extends ServiceImpl impl maxUserId = queryOrderResultSet.getLong("max_id"); // 获取历史最大用户id long maxUserIdHistory = StringUtil.isNullOrEmpty(redisUtil.getFromString(maximumUserIdKey)) ? CommonConstant.MAXIMUM_USER_ID : Long.parseLong(redisUtil.getFromString(maximumUserIdKey)); + log.info("Code-Create Maximum User ID last time : {}", maxUserIdHistory); + log.info("Currently Code-Create Maximum User ID : {}", maxUserIdHistory); if (maxUserId > maxUserIdHistory){ // 查出新增用户的邮箱 PreparedStatement newUserEmail = connection.prepareStatement(QUERY_NEW_USER_EMAIL); @@ -1476,12 +1569,14 @@ public class AccountServiceImpl extends ServiceImpl impl // 将新增用户添加到AiDA,身份为游客 if (!newUsersInfo.isEmpty()){ newUsersInfo.forEach(userInfo -> { + long epochMilli = Instant.now().toEpochMilli(); Account account = new Account(); account.setUserEmail(userInfo.get("email")); account.setUserName(userInfo.get("username")); account.setUserPassword("Third-000000"); account.setLanguage(Language.ENGLISH.name()); - account.setValidStartTime(Instant.now().toEpochMilli()); + account.setValidStartTime(epochMilli); + account.setValidEndTime(epochMilli); account.setCreateDate(new Date()); account.setIsTrial(0); account.setIsBeginner(1); @@ -1497,6 +1592,16 @@ public class AccountServiceImpl extends ServiceImpl impl } } + public static void main(String[] args) { + List a = new ArrayList<>(); + a.add("1023316923@qq.com"); + a.add("Malinyuquan@gmail.com"); + + if (a.contains("malinyuquan@gmail.com")) { + log.info("aaaaaaaaaaaaaaaaaaaaa"); + } + } + private static final String QUERY_PAID_CUSTOMER_EMAIL = "SELECT distinct c.email " + "FROM `pmr_wc_order_stats` o " + "inner join `pmr_wc_customer_lookup` c " + @@ -1581,8 +1686,9 @@ public class AccountServiceImpl extends ServiceImpl impl UpdateWrapper accountUpdateWrapper = new UpdateWrapper<>(); // 刷新账号有效期截止之前的年付用户的积分 long epochMilli = Instant.now().toEpochMilli(); - accountUpdateWrapper.lambda().set(Account::getCredits, CreditsEventsEnum.INIT_WEEKLY.getValue()) - .eq(Account::getSystemUser,1).or().eq(Account::getSystemUser,2) + accountUpdateWrapper.lambda().set(Account::getCredits, CreditsEventsEnum.RESET_YEAR_CREDITS.getValue()) + .eq(Account::getSystemUser,1) +// .or().eq(Account::getSystemUser,2) .gt(Account::getValidEndTime, epochMilli); baseMapper.update(null,accountUpdateWrapper); } @@ -1651,7 +1757,7 @@ public class AccountServiceImpl extends ServiceImpl impl } public void toVisitor(Account account){ - accountMapper.toVisitor(account.getId(), new Date()); + accountMapper.toVisitor(account.getId()); } public List setUserValidToDayEnd(){ @@ -1788,42 +1894,42 @@ public class AccountServiceImpl extends ServiceImpl impl return redisUtil.getPersonalHomepageViewCount(accountId); } - // 获取当前用户30天内 剩余昵称修改次数 - public Map getNicknameModifyTimes(){ + // 获取当前用户本月内 剩余昵称修改次数 + public Long getNicknameModifyTimes(){ Long accountId = UserContext.getUserHolder().getId(); String key = RedisUtil.NICKNAME_MODIFY_TIMES + accountId; Long times = redisUtil.getIncrementCount(key); - HashMap resp = new HashMap<>(); - resp.put("remainingTimes", 5L - times); - resp.put("remainingDays", redisUtil.getExpire(key) == -1 ? 30L : (long) Math.ceil((double) redisUtil.getExpire(key) / (24 * 60 * 60))); - return resp; +// HashMap resp = new HashMap<>(); +// resp.put("remainingTimes", 5L - times); +// resp.put("remainingDays", redisUtil.getExpire(key) == -1 ? 30L : (long) Math.ceil((double) redisUtil.getExpire(key) / (24 * 60 * 60))); + return 5L - times; } - // 修改用户名 允许用户30天内修改5次 + // 修改用户名 允许用户当前月内修改5次 @Transactional(rollbackFor = Exception.class) public void editUserName(String newUserName){ Long accountId = UserContext.getUserHolder().getId(); // 判断当前用户是否还有修改昵称的次数 - Map remainTimes = getNicknameModifyTimes(); - Long remainingModifyTimes = remainTimes.get("remainingTimes"); +// Map remainTimes = getNicknameModifyTimes(); + Long remainingModifyTimes = getNicknameModifyTimes(); if (remainingModifyTimes > 0){ - Account account = new Account().setUserName(newUserName); - account.setId(accountId); - baseMapper.updateById(account); - - String key = RedisUtil.NICKNAME_MODIFY_TIMES + accountId; - // 先判断有没有这个key,若没有这个key, 需要为key添加有效期 - if (remainingModifyTimes == 5){ - redisUtil.setKeyExpire(key, 30L); + Account account = baseMapper.selectById(accountId); + // 当新昵称与旧昵称不同时,修改 + if (!account.getUserName().equals(newUserName)){ + account.setUserName(newUserName); + account.setId(accountId); + baseMapper.updateById(account); + // 每个月初清除RedisUtil.NICKNAME_MODIFY_TIMES下的所有记录 + String key = RedisUtil.NICKNAME_MODIFY_TIMES + accountId; + // 增加修改次数 + redisUtil.increaseCount(key); } - // 增加修改次数 - redisUtil.increaseCount(key); }else { throw new BusinessException("remaining.modifications", 1); } } - // 验证是否是本人进行邮箱绑定更改 + /*// 验证是否是本人进行邮箱绑定更改 public void verifyUserEmail(String verifyCode){ // 向旧邮箱发送验证码,以保证是当前邮箱拥有者在进行更改 String userEmail = baseMapper.selectById(UserContext.getUserHolder().getId()).getUserEmail(); @@ -1856,7 +1962,6 @@ public class AccountServiceImpl extends ServiceImpl impl // 验证激活链接 public void activateNewEmail(String token){ // 获取链接地址信息,更新指定用户邮箱 - String emailAndId = jwtTokenHelper.parseToEmailAndId(token); String newMailbox = emailAndId.substring(0, emailAndId.lastIndexOf("_")); String accountId = emailAndId.substring(emailAndId.lastIndexOf("_") + 1); @@ -1878,7 +1983,7 @@ public class AccountServiceImpl extends ServiceImpl impl account.setId(Long.parseLong(accountId)); baseMapper.updateById(account); log.info("邮箱绑定更改完成,用户id:{},新邮箱:{}", accountId, newMailbox); - } + }*/ @Override public String googleCallback(String code, HttpSession session) { @@ -1984,9 +2089,9 @@ public class AccountServiceImpl extends ServiceImpl impl try { // 判断用户语言,调用相应邮件发送方法 if (Language.CHINESE_SIMPLIFIED.name().equals(account.getLanguage())) { - SendEmailUtil.temporaryUpgrade(account, null, 0); +// SendEmailUtil.temporaryUpgrade(account, null, 0); } else { - SendEmailUtil.temporaryUpgrade(account, null, 1); +// SendEmailUtil.temporaryUpgrade(account, null, 1); } } catch (Exception e) { // 捕获单个用户的发送失败异常,记录日志但不中断流程 @@ -1997,4 +2102,688 @@ public class AccountServiceImpl extends ServiceImpl impl } } + + @Override + public AccountLoginVO enterpriseLogin(AccountLoginDTO accountDTO) { + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(Account::getUserName, accountDTO.getUserName()); + qw.lambda().eq(Account::getOrganizationName, accountDTO.getOrganizationName()); + List accounts = accountMapper.selectList(qw); + if (CollectionUtil.isEmpty(accounts)) { + throw new BusinessException("Username or enterprise name incorrect."); + } + qw.lambda().eq(Account::getUserPassword, accountDTO.getPassword()); + accounts = accountMapper.selectList(qw); + if (CollectionUtil.isEmpty(accounts)) { + throw new BusinessException("Password error."); + } + Account account = accounts.get(0); + AccountLoginVO response = CopyUtil.copyObject(account, AccountLoginVO.class); + response.setEmail(account.getUserEmail()); + String token = LocalCacheUtils.getTokenCache(String.valueOf(account.getId())); + if (StringUtils.isNotBlank(token)) { + //用户已登入 + response.setToken(token); + } else { + response.setToken(createAccountToken(account)); + } + response.setUserId(account.getId()); + response.setSystemUser(account.getSystemUser()); + // 设置头像 + String avatar; + if (StringUtil.isNullOrEmpty(account.getAvatar())){ + avatar = CommonConstant.DEFAULT_AVATAR; + }else { + avatar = account.getAvatar(); + } + response.setAvatar(minioUtil.getPreSignedUrl(avatar, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); + response.setFolloweeCount(portfolioService.getFolloweeCount(account.getId())); + response.setFollowerCount(portfolioService.getFollowerCount(account.getId())); + //判断是否常用ip 不是则发邮件提示 +// calculateExceptionIp(RequestInfoUtil.getIpAddress(request), account); + return response; + } + + @Override + public AccountLoginVO schoolLogin(AccountLoginDTO accountDTO) { + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(Account::getUserName, accountDTO.getUserName()); + qw.lambda().eq(Account::getOrganizationName, accountDTO.getOrganizationName()); + List accounts = accountMapper.selectList(qw); + if (CollectionUtil.isEmpty(accounts)) { + throw new BusinessException("Username or school name incorrect."); + } + qw.lambda().eq(Account::getUserPassword, accountDTO.getPassword()); + accounts = accountMapper.selectList(qw); + if (CollectionUtil.isEmpty(accounts)) { + throw new BusinessException("Password error."); + } + Account account = accounts.get(0); + AccountLoginVO response = CopyUtil.copyObject(account, AccountLoginVO.class); + response.setEmail(account.getUserEmail()); + String token = LocalCacheUtils.getTokenCache(String.valueOf(account.getId())); + if (StringUtils.isNotBlank(token)) { + //用户已登入 + response.setToken(token); + } else { + response.setToken(createAccountToken(account)); + } + response.setUserId(account.getId()); + response.setSystemUser(account.getSystemUser()); + // 设置头像 + String avatar; + if (StringUtil.isNullOrEmpty(account.getAvatar())){ + avatar = CommonConstant.DEFAULT_AVATAR; + }else { + avatar = account.getAvatar(); + } + response.setAvatar(minioUtil.getPreSignedUrl(avatar, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); + response.setFolloweeCount(portfolioService.getFolloweeCount(account.getId())); + response.setFollowerCount(portfolioService.getFollowerCount(account.getId())); + //判断是否常用ip 不是则发邮件提示 +// calculateExceptionIp(RequestInfoUtil.getIpAddress(request), account); + return response; + } + + @Override + public Boolean addSubAccount(AddSubAccountDTO addSubAccountDTO) { + if (null == addSubAccountDTO.getId()) { + AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder(); + Account account = accountMapper.selectById(authPrincipalVo.getId()); + + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(Account::getOrganizationName, account.getOrganizationName()); + List accounts = accountMapper.selectList(qw); + if (accounts.size() >= account.getSubAccountNum()) { + throw new BusinessException("The maximum number of sub accounts that can be created has been reached."); + } + + qw.lambda().eq(Account::getUserName, addSubAccountDTO.getUserName()); + accounts = accountMapper.selectList(qw); + if (CollectionUtil.isNotEmpty(accounts)) { + throw new BusinessException("The enterprise already has an account with the same username."); + } + + Account subAccount = CopyUtil.copyObject(addSubAccountDTO, Account.class); + subAccount.setSystemUser(6); + subAccount.setValidStartTime(account.getValidStartTime()); + subAccount.setValidEndTime(account.getValidEndTime()); + subAccount.setLanguage(Language.ENGLISH.name()); + subAccount.setCreateDate(new Date()); + subAccount.setIsTrial(0); + subAccount.setIsBeginner(1); + subAccount.setParentId(account.getParentId()); + accountMapper.insert(subAccount); + }else { + Account subAccount = CopyUtil.copyObject(addSubAccountDTO, Account.class); + accountMapper.updateById(subAccount); + } + + + return Boolean.TRUE; + } + + @Override + public Boolean deleteSubAccount(AddSubAccountDTO addSubAccountDTO) { + accountMapper.deleteBatchIds(addSubAccountDTO.getDeleteIdList()); + return Boolean.TRUE; + } + + @Override + public PageBaseResponse subAccountList(SubAccountPageDTO subAccountPageDTO) { + AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder(); + Account account = accountMapper.selectById(authPrincipalVo); + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().ne(Account::getId, account.getId()); + qw.lambda().eq(Account::getOrganizationName, account.getOrganizationName()); + // 执行分页查询 + IPage page = accountMapper.selectPage(new Page<>(subAccountPageDTO.getPage(), subAccountPageDTO.getSize()), qw); + + return PageBaseResponse.success(page); + } + + @Override + public Account accountDetail(Long id) { + return accountMapper.selectById(id); + } + + @Override + public AccountLoginVO parseGoogleCredential(String credential, Integer type) { + // 配置 Google ID Token 验证器 + GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder( + new NetHttpTransport(), + JacksonFactory.getDefaultInstance()) + .setAudience(Collections.singletonList(CLIENT_ID)) + .build(); + + // 验证并解析 ID Token + GoogleIdToken idToken = null; + try { + idToken = verifier.verify(credential); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + + + if (idToken != null) { + GoogleIdToken.Payload payload = idToken.getPayload(); + + // 提取用户信息 + String userId = payload.getSubject(); + String email = payload.getEmail(); + String name = (String) payload.get("name"); + String pictureUrl = (String) payload.get("picture"); + log.info(userId); + log.info(email); + log.info(name); + Account account = new Account(); + if (type == 1) { + // 注册 + QueryWrapper accountExtendQW = new QueryWrapper<>(); + accountExtendQW.lambda().eq(AccountExtend::getAuthType, "Google"); + accountExtendQW.lambda().eq(AccountExtend::getAuth, userId); + List accountExtends = accountExtendMapper.selectList(accountExtendQW); + if (CollectionUtil.isNotEmpty(accountExtends)) { + throw new BusinessException("This Google account has been registered for AiDA, please use Google Quick Login directly."); + }else { + QueryWrapper accountQueryWrapper = new QueryWrapper<>(); + accountQueryWrapper.lambda().eq(Account::getUserEmail, email); // 根据邮箱查询用户 + List accounts = accountMapper.selectList(accountQueryWrapper); + if (CollectionUtil.isNotEmpty(accounts)) { + account = accounts.get(0); + AccountExtend accountExtendInsert = new AccountExtend(); + accountExtendInsert.setAuth(userId); + accountExtendInsert.setAuthType("Google"); + accountExtendInsert.setHeadImgUrl(pictureUrl); + accountExtendInsert.setName(name); + accountExtendInsert.setAccountId(account.getId()); + accountExtendMapper.insert(accountExtendInsert); + }else { + Account newUser = new Account(); + newUser.setUserEmail(email); + newUser.setUserName(name); + newUser.setUserPassword("Third-000000"); + newUser.setLanguage(Language.ENGLISH.name()); + newUser.setValidStartTime(System.currentTimeMillis()); + newUser.setValidEndTime(toDayEnd(Instant.now().plus(5, ChronoUnit.DAYS).toEpochMilli())); + newUser.setCreateDate(new Date()); + newUser.setIsTrial(1); + newUser.setIsBeginner(1); + newUser.setCredits(BigDecimal.valueOf(100)); + newUser.setSystemUser(3); + accountMapper.insert(newUser); + + account = newUser; + + AccountExtend accountExtendInsert = new AccountExtend(); + accountExtendInsert.setAuth(userId); + accountExtendInsert.setAuthType("Google"); + accountExtendInsert.setHeadImgUrl(pictureUrl); + accountExtendInsert.setName(name); + accountExtendInsert.setAccountId(newUser.getId()); + accountExtendMapper.insert(accountExtendInsert); + } + } + }else { + // 登录 + QueryWrapper accountExtendQW = new QueryWrapper<>(); + accountExtendQW.lambda().eq(AccountExtend::getAuthType, "Google"); + accountExtendQW.lambda().eq(AccountExtend::getAuth, userId); + List accountExtends = accountExtendMapper.selectList(accountExtendQW); + if (CollectionUtil.isNotEmpty(accountExtends)) { + AccountExtend accountExtend = accountExtends.get(0); + account = accountMapper.selectById(accountExtend.getAccountId()); + }else { + throw new BusinessException("This Google account has not been bound to an AiDA account yet. Please register an AiDA account and bind it to a Google account, or bind the Google account to an existing AiDA account."); + } + } + AccountLoginVO response = CopyUtil.copyObject(account, AccountLoginVO.class); + response.setEmail(account.getUserEmail()); + String token = LocalCacheUtils.getTokenCache(String.valueOf(account.getId())); + if (StringUtils.isNotBlank(token)) { + //用户已登入 + response.setToken(token); + } else { + response.setToken(createAccountToken(account)); + } + response.setUserId(account.getId()); + response.setSystemUser(account.getSystemUser()); + // 设置头像 + String avatar; + if (StringUtil.isNullOrEmpty(account.getAvatar())){ + avatar = CommonConstant.DEFAULT_AVATAR; + }else { + avatar = account.getAvatar(); + } + response.setAvatar(minioUtil.getPreSignedUrl(avatar, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); + response.setFolloweeCount(portfolioService.getFolloweeCount(account.getId())); + response.setFollowerCount(portfolioService.getFollowerCount(account.getId())); + return response; + } else { + throw new IllegalArgumentException("Invalid ID token."); + } + } + + private static final String WECHAT_ACCESS_TOKEN_URL = + "https://api.weixin.qq.com/sns/oauth2/access_token"; + + private static final String APP_ID = "wxcfb92eb28d6385f5"; + private static final String APP_SECRET = "e5592c691756455b2d03ebfd21fc3131"; + + @Override + public AccountLoginVO parseWeChatCode(String code, Integer type) { + // 1. 获取 access_token 和 openid + JSONObject accessTokenResponse = getAccessTokenFromWeChat(code); + String accessToken = accessTokenResponse.getString("access_token"); + String openId = accessTokenResponse.getString("openid"); + + if (StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(openId)) { + throw new RuntimeException("微信接口返回数据缺失: " + accessTokenResponse.toJSONString()); + } + + // 2. 获取用户信息 + JSONObject userInfoResponse = getUserInfoFromWeChat(accessToken, openId); + + // 提取 unionid 和 nickname + String unionId = userInfoResponse.getString("unionid"); + String userName = userInfoResponse.getString("nickname"); + String headimgurl = userInfoResponse.getString("headimgurl"); + + if (unionId == null) { + throw new IllegalArgumentException("无法获取 unionid,请检查微信开发平台配置"); + } + Account account = new Account(); + if (type == 1) { + // 注册 + // 检查数据库中是否已有该unionid + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.lambda().eq(AccountExtend::getAuthType, "WeChat"); + queryWrapper.lambda().eq(AccountExtend::getAuth, unionId); + List accountExtends = accountExtendMapper.selectList(queryWrapper); + if (CollectionUtil.isNotEmpty(accountExtends)) { + throw new BusinessException("This Wechat account has been registered for AiDA, please use Wechat Quick Login directly."); + }else { + // 创建新用户(自动注册) + Account newUser = new Account(); + newUser.setUserName(userName); + newUser.setUserPassword("Third-000000"); + newUser.setLanguage(Language.ENGLISH.name()); + newUser.setValidStartTime(System.currentTimeMillis()); + newUser.setValidEndTime(toDayEnd(Instant.now().plus(5, ChronoUnit.DAYS).toEpochMilli())); + newUser.setCreateDate(new Date()); + newUser.setIsTrial(1); + newUser.setIsBeginner(1); + newUser.setCredits(BigDecimal.valueOf(100)); + newUser.setSystemUser(3); + accountMapper.insert(newUser); + + AccountExtend accountExtendInsert = new AccountExtend(); + accountExtendInsert.setAuth(unionId); + accountExtendInsert.setAuthType("WeChat"); + accountExtendInsert.setHeadImgUrl(headimgurl); + accountExtendInsert.setName(userName); + accountExtendInsert.setAccountId(newUser.getId()); + accountExtendMapper.insert(accountExtendInsert); + + account = newUser; + } + }else { + // 登录 + // 检查数据库中是否已有该unionid + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.lambda().eq(AccountExtend::getAuthType, "WeChat"); + queryWrapper.lambda().eq(AccountExtend::getAuth, unionId); + List accountExtends = accountExtendMapper.selectList(queryWrapper); + if (CollectionUtil.isNotEmpty(accountExtends)) { + AccountExtend accountExtend = accountExtends.get(0); + account = accountMapper.selectById(accountExtend.getAccountId()); + }else { + throw new BusinessException("This WeChat account has not been bound to an AiDA account yet. Please register an AiDA account and bind it to the WeChat account, or bind the WeChat account to an existing AiDA account."); + } + } + + AccountLoginVO response = CopyUtil.copyObject(account, AccountLoginVO.class); + response.setEmail(account.getUserEmail()); + String token = LocalCacheUtils.getTokenCache(String.valueOf(account.getId())); + if (StringUtils.isNotBlank(token)) { + //用户已登入 + response.setToken(token); + } else { + response.setToken(createAccountToken(account)); + } + response.setUserId(account.getId()); + response.setSystemUser(account.getSystemUser()); + // 设置头像 + String avatar; + if (StringUtil.isNullOrEmpty(account.getAvatar())){ + avatar = CommonConstant.DEFAULT_AVATAR; + }else { + avatar = account.getAvatar(); + } + response.setAvatar(minioUtil.getPreSignedUrl(avatar, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); + response.setFolloweeCount(portfolioService.getFolloweeCount(account.getId())); + response.setFollowerCount(portfolioService.getFollowerCount(account.getId())); + return response; + } + + private static final String WECHAT_USER_INFO_URL = "https://api.weixin.qq.com/sns/userinfo"; + private JSONObject getUserInfoFromWeChat(String accessToken, String openId) { + // 构造微信用户信息接口的 URL + String url = String.format( + "%s?access_token=%s&openid=%s&lang=zh_CN", + WECHAT_USER_INFO_URL, accessToken, openId + ); + + // 调用微信接口 + RestTemplate restTemplate = new RestTemplate(); + String response = restTemplate.getForObject(url, String.class); + + // 强制校正编码为 UTF-8(如果返回的是 ISO-8859-1 编码) + if (!isUTF8(response)) { // 检查编码方法,下面会提供 + byte[] bytes = response.getBytes(StandardCharsets.ISO_8859_1); + response = new String(bytes, StandardCharsets.UTF_8); + } + + // 打印调试信息 + log.info("WeChat user info response: {}", response); + + // 转换为 JSON 对象 + JSONObject jsonResponse = JSONObject.parseObject(response); + if (jsonResponse.containsKey("errcode")) { + throw new RuntimeException("微信用户信息接口调用失败: " + jsonResponse.getString("errmsg")); + } + + return jsonResponse; + } + + private boolean isUTF8(String text) { + try { + byte[] bytes = text.getBytes(StandardCharsets.ISO_8859_1); + String decoded = new String(bytes, StandardCharsets.UTF_8); + return decoded.equals(text); + } catch (Exception e) { + return false; + } + } + + + private JSONObject getAccessTokenFromWeChat(String code) { + // 构造微信接口请求 URL + String url = String.format( + "%s?appid=%s&secret=%s&code=%s&grant_type=authorization_code", + WECHAT_ACCESS_TOKEN_URL, APP_ID, APP_SECRET, code + ); + + // 调用微信接口 + RestTemplate restTemplate = new RestTemplate(); + String response = restTemplate.getForObject(url, String.class); + + // 转换为 JSON 对象 + JSONObject jsonResponse = JSONObject.parseObject(response); + if (jsonResponse.containsKey("errcode")) { + throw new RuntimeException("微信接口调用失败: " + jsonResponse.getString("errmsg")); + } + + return jsonResponse; + } + + @Override + public AccountLoginVO getAccountDetail() { + AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder(); + Long accountId = authPrincipalVo.getId(); + Account account = accountMapper.selectById(accountId); + + AccountLoginVO response = CopyUtil.copyObject(account, AccountLoginVO.class); + response.setEmail(account.getUserEmail()); + String token = LocalCacheUtils.getTokenCache(String.valueOf(account.getId())); + if (StringUtils.isNotBlank(token)) { + //用户已登入 + response.setToken(token); + } else { + response.setToken(createAccountToken(account)); + } + response.setUserId(account.getId()); + response.setSystemUser(account.getSystemUser()); + // 设置头像 + String avatar; + if (StringUtil.isNullOrEmpty(account.getAvatar())){ + avatar = CommonConstant.DEFAULT_AVATAR; + }else { + avatar = account.getAvatar(); + } + response.setAvatar(minioUtil.getPreSignedUrl(avatar, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)); + response.setFolloweeCount(portfolioService.getFolloweeCount(account.getId())); + response.setFollowerCount(portfolioService.getFollowerCount(account.getId())); + + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(AccountExtend::getAccountId, response.getUserId()); + List accountExtends = accountExtendMapper.selectList(qw); + if (CollectionUtil.isNotEmpty(accountExtends)) { + response.setAccountExtendList(accountExtends); + } + response.setLanguage(Language.valueOf(account.getLanguage()).name()); + SubscriptionInfo subscriptionInfo = stripeService.getLatestSubscriptionInfoByAccountId(accountId); + if (!Objects.isNull(subscriptionInfo)) { + response.setSubscriptionId(subscriptionInfo.getSubscriptionId()); + response.setSubscriptionType(subscriptionInfo.getType()); + response.setStatus(subscriptionInfo.getStatus()); + response.setExpireTime(String.valueOf(subscriptionInfo.getCurrentPeriodEnd())); + response.setAutoRenewal(subscriptionInfo.getStatus().equals("active")); + } + + Affiliate affiliate = affiliateService.getByAccountId(accountId); + if (!Objects.isNull(affiliate) && affiliate.getStatus().equals("Active")) { + response.setAffiliate(true); + } + + response.setUsernameModify(getNicknameModifyTimes()); + return response; + } + + @Override + public AccountExtend bindGoogle(String credential) { + // 配置 Google ID Token 验证器 + GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder( + new NetHttpTransport(), + JacksonFactory.getDefaultInstance()) + .setAudience(Collections.singletonList(CLIENT_ID)) + .build(); + + // 验证并解析 ID Token + GoogleIdToken idToken = null; + try { + idToken = verifier.verify(credential); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + + + if (idToken != null) { + GoogleIdToken.Payload payload = idToken.getPayload(); + + // 提取用户信息 + String userId = payload.getSubject(); + String email = payload.getEmail(); + String name = (String) payload.get("name"); + String pictureUrl = (String) payload.get("picture"); + + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(AccountExtend::getAuth, userId); + qw.lambda().eq(AccountExtend::getAuthType, "Google"); + List accountExtends = accountExtendMapper.selectList(qw); + + if (CollectionUtil.isNotEmpty(accountExtends)) { + throw new BusinessException("The Google has been bound."); + } + + AccountExtend accountExtendInsert = new AccountExtend(); + accountExtendInsert.setAuth(userId); + accountExtendInsert.setAuthType("Google"); + accountExtendInsert.setHeadImgUrl(pictureUrl); + accountExtendInsert.setName(name); + + AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder(); + accountExtendInsert.setAccountId(authPrincipalVo.getId()); + accountExtendMapper.insert(accountExtendInsert); + + return accountExtendInsert; + } else { + throw new IllegalArgumentException("Invalid ID token."); + } + } + + @Override + @Transactional + public AccountExtend bindWeChat(String code) { + // 1. 获取 access_token 和 openid + JSONObject accessTokenResponse = getAccessTokenFromWeChat(code); + String accessToken = accessTokenResponse.getString("access_token"); + String openId = accessTokenResponse.getString("openid"); + + if (StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(openId)) { + throw new RuntimeException("微信接口返回数据缺失: " + accessTokenResponse.toJSONString()); + } + + // 2. 获取用户信息 + JSONObject userInfoResponse = getUserInfoFromWeChat(accessToken, openId); + + // 提取 unionid 和 nickname + String unionId = userInfoResponse.getString("unionid"); + String userName = userInfoResponse.getString("nickname"); + String headimgurl = userInfoResponse.getString("headimgurl"); + if (unionId == null) { + throw new IllegalArgumentException("无法获取 unionid,请检查微信开发平台配置"); + } + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(AccountExtend::getAuth, unionId); + qw.lambda().eq(AccountExtend::getAuthType, "WeChat"); + List accountExtends = accountExtendMapper.selectList(qw); + + if (CollectionUtil.isNotEmpty(accountExtends)) { + AccountExtend accountExtend = accountExtends.get(0); + AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder(); + + accountMapper.deleteById(accountExtend.getAccountId()); + + accountExtend.setAccountId(authPrincipalVo.getId()); + accountExtendMapper.updateById(accountExtend); + return accountExtend; + } + + AccountExtend accountExtendInsert = new AccountExtend(); + accountExtendInsert.setAuth(unionId); + accountExtendInsert.setAuthType("WeChat"); + accountExtendInsert.setHeadImgUrl(headimgurl); + accountExtendInsert.setName(userName); + + AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder(); + accountExtendInsert.setAccountId(authPrincipalVo.getId()); + accountExtendMapper.insert(accountExtendInsert); + + return accountExtendInsert; + } + + @Override + public Boolean unbindWeChat() { + AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder(); + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(AccountExtend::getAccountId, authPrincipalVo.getId()); + qw.lambda().eq(AccountExtend::getAuthType, "WeChat"); + accountExtendMapper.delete(qw); + return Boolean.TRUE; + } + + @Override + public Boolean unbindGoogle() { + AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder(); + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(AccountExtend::getAccountId, authPrincipalVo.getId()); + qw.lambda().eq(AccountExtend::getAuthType, "Google"); + accountExtendMapper.delete(qw); + return Boolean.TRUE; + } + + @Override + @Transactional + public BindEmailVO bindEmail(String email) { + AuthPrincipalVo userHolder = UserContext.getUserHolder(); + + BindEmailVO result = new BindEmailVO(); + + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(Account::getUserEmail, email); + List accounts = accountMapper.selectList(qw); + if (CollectionUtil.isNotEmpty(accounts)) { + Account account = accounts.get(0); + + QueryWrapper accountExtendQW = new QueryWrapper<>(); + accountExtendQW.lambda().eq(AccountExtend::getAccountId, userHolder.getId()); + accountExtendQW.lambda().eq(AccountExtend::getAuthType, "Wechat"); + AccountExtend accountExtend = accountExtendMapper.selectOne(accountExtendQW); + accountExtend.setAccountId(account.getId()); + accountExtendMapper.updateById(accountExtend); + + accountMapper.deleteById(userHolder.getId()); + + String token = LocalCacheUtils.getTokenCache(String.valueOf(account.getId())); + if (StringUtils.isNotBlank(token)) { + //用户已登入 + result.setToken(token); + } else { + result.setToken(createAccountToken(account)); + } + return result; + } + Account account = accountMapper.selectById(userHolder.getId()); + account.setUserEmail(email); + accountMapper.updateById(account); + return result; + } + + public void updateAccountValidity(Long accountId, Long currentPeriodEnd){ + // 不管当前用户的账号是否到期,都根据付款信息重置账号到期时间 + Account account = accountMapper.selectById(accountId); + account.setValidEndTime(currentPeriodEnd * 1000); + + accountMapper.updateById(account); + } + + public void updateUserRoleAndCredits(Long accountId, String type){ + Account account = accountMapper.selectById(accountId); + switch (type) { + case "month": + account.setSystemUser(2); + account.setCredits(BigDecimal.valueOf(Long.parseLong(CreditsEventsEnum.INIT_MONTHLY.getValue()))); + break; + case "year": + account.setSystemUser(1); + account.setCredits(BigDecimal.valueOf(Long.parseLong(CreditsEventsEnum.INIT_YEARLY.getValue()))); + break; + case "day": + account.setSystemUser(3); + account.setCredits(BigDecimal.valueOf(Long.parseLong(CreditsEventsEnum.INIT_WEEKLY.getValue()))); + break; + } + + accountMapper.updateById(account); + } + + public void updateUserInfo(String country, String occupation){ + Long accountId = UserContext.getUserHolder().getId(); + Account account = accountMapper.selectById(accountId); + boolean flag = false; + if (!StringUtil.isNullOrEmpty(country) && !country.equals(account.getCountry())) { + account.setCountry(country.trim()); + flag = true; + } + if (!StringUtil.isNullOrEmpty(occupation) && !occupation.equals(account.getOccupation())) { + account.setOccupation(occupation.trim()); + flag = true; + } + if(flag) accountMapper.updateById(account); + } } diff --git a/src/main/java/com/ai/da/service/impl/AffiliateServiceImpl.java b/src/main/java/com/ai/da/service/impl/AffiliateServiceImpl.java new file mode 100644 index 00000000..27a8ac6a --- /dev/null +++ b/src/main/java/com/ai/da/service/impl/AffiliateServiceImpl.java @@ -0,0 +1,332 @@ +package com.ai.da.service.impl; + +import com.ai.da.common.config.exception.BusinessException; +import com.ai.da.common.constant.CommonConstant; +import com.ai.da.common.context.UserContext; +import com.ai.da.common.response.PageBaseResponse; +import com.ai.da.common.response.ResultEnum; +import com.ai.da.common.utils.CopyUtil; +import com.ai.da.common.utils.RedisUtil; +import com.ai.da.common.utils.SendEmailUtil; +import com.ai.da.mapper.primary.AffiliateIncomeMapper; +import com.ai.da.mapper.primary.AffiliateMapper; +import com.ai.da.mapper.primary.SubscriptionInfoMapper; +import com.ai.da.mapper.primary.entity.*; +import com.ai.da.model.dto.AffiliateEmailParamsDTO; +import com.ai.da.model.dto.AffiliateQueryDTO; +import com.ai.da.model.vo.AffiliateInvitationDetailsVO; +import com.ai.da.model.vo.AffiliateVO; +import com.ai.da.model.vo.AuthPrincipalVo; +import com.ai.da.service.AccountService; +import com.ai.da.service.AffiliateService; +import com.ai.da.service.OrderInfoService; +import com.ai.da.service.PaymentInfoService; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.mysql.cj.util.StringUtils; +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.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +@Service +@Slf4j +public class AffiliateServiceImpl extends ServiceImpl implements AffiliateService { + + @Resource + private OrderInfoService orderInfoService; + + @Resource + private AccountService accountService; + + @Resource + private PaymentInfoService paymentInfoService; + + @Resource + private SubscriptionInfoMapper subscriptionInfoMapper; + + @Resource + private AffiliateIncomeMapper affiliateIncomeMapper; + + @Resource + private RedisUtil redisUtil; + + // 推广者注册 + public Boolean registerAsAnAffiliate(String promotionMethod){ + AuthPrincipalVo userHolder = UserContext.getUserHolder(); + // 判断该用户是否已注册 + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("account_id", userHolder.getId()); + Affiliate affiliate = baseMapper.selectOne(qw); + if (Objects.isNull(affiliate)){ + affiliate = new Affiliate(); + affiliate.setAccountId(userHolder.getId()); + affiliate.setStatus("Pending"); + affiliate.setCreateTime(LocalDateTime.now()); + affiliate.setPromotionMethod(promotionMethod); + baseMapper.insert(affiliate); + // 邮件通知审批者 +// String merchantEmail = "kimwong@code-create.com.hk"; + String developer = "xupei3360@163.com"; + String[] receiverEmail = {/*merchantEmail, */developer}; + SendEmailUtil.affiliateEmailReminder(receiverEmail, new AffiliateEmailParamsDTO(userHolder.getUsername(), promotionMethod), "new"); + }else { + throw new BusinessException("You have registered an Affiliate", ResultEnum.PROMPT.getCode()); + } + return true; + } + + public PageBaseResponse getAffiliateList(AffiliateQueryDTO affiliateQueryDTO){ + log.info("parameter => {}", affiliateQueryDTO.toString()); + + int offset = (affiliateQueryDTO.getPage() - 1) * affiliateQueryDTO.getSize(); + List affiliateList = baseMapper.getAffiliateList(affiliateQueryDTO.getStatus(), + affiliateQueryDTO.getStartTime(), + affiliateQueryDTO.getEndTime(), + affiliateQueryDTO.getOrder(), + affiliateQueryDTO.getAffiliateId(), + affiliateQueryDTO.getSize(), + offset + ); + if (CollectionUtils.isEmpty(affiliateList)) { + return PageBaseResponse.success(new Page<>()); + }else { + int totalCount = baseMapper.queryAffiliateTotalCount(affiliateQueryDTO.getStatus(), + affiliateQueryDTO.getStartTime(), + affiliateQueryDTO.getEndTime(), + affiliateQueryDTO.getAffiliateId() + ); + IPage orderListVOIPage = new Page<>(); + Integer size = affiliateQueryDTO.getSize(); + orderListVOIPage.setSize(size); + orderListVOIPage.setRecords(affiliateList); + orderListVOIPage.setCurrent(affiliateQueryDTO.getPage()); + orderListVOIPage.setPages((long)Math.ceil((double) totalCount / size)); + orderListVOIPage.setTotal(totalCount); + return PageBaseResponse.success(orderListVOIPage); + } + /*QueryWrapper qw = new QueryWrapper<>(); + qw.eq(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getStatus()), "status", affiliateQueryDTO.getStatus()) + .gt(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getStartTime()), "create_time", affiliateQueryDTO.getStartTime()) + .lt(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getEndTime()), "create_time", affiliateQueryDTO.getEndTime()) + .eq(!Objects.isNull(affiliateQueryDTO.getAffiliateId()), "id", affiliateQueryDTO.getAffiliateId()) + .orderByDesc(affiliateQueryDTO.getOrder().equals("DESC"), "create_time"); + Page affiliatePage = baseMapper.selectPage(new Page<>(affiliateQueryDTO.getPage(), affiliateQueryDTO.getSize()), qw); + affiliatePage.convert((Function) affiliate-> { + AffiliateVO affiliateVO = CopyUtil.copyObject(affiliate, AffiliateVO.class); + affiliateVO.setUsername(); + }); + return affiliatePage;*/ + } + + public AffiliateVO personalAffiliateCenter(){ + QueryWrapper qw = new QueryWrapper<>(); + Long accountId = UserContext.getUserHolder().getId(); + qw.eq("account_id", accountId); + Affiliate affiliate = baseMapper.selectOne(qw); + AffiliateVO affiliateVO = CopyUtil.copyObject(affiliate, AffiliateVO.class); + affiliateVO.setLinkViewCount(getAffiliateLinkViewCount(affiliate.getId())); + return affiliateVO; + } + + public double[] getPersonalMonthlyIncome(int year){ + Long accountId = UserContext.getUserHolder().getId(); + List> personalMonthlyIncome = affiliateIncomeMapper.getPersonalMonthlyIncome(accountId, year); + double[] commissions = new double[12]; + personalMonthlyIncome.forEach(income -> { + int month = Integer.parseInt(income.get("yearMonth").toString()); + commissions[month-1] = (double)income.get("totalCommission"); + }); + + return commissions; + } + + // 审批申请 + public Boolean applicationApproval(Long id, Boolean isApproved){ + Affiliate affiliate = baseMapper.selectById(id); + + // 1、更新db状态 + if (isApproved){ + // 更新状态 + affiliate.setStatus("Active"); + affiliate.setApproved(true); + affiliate.setLink(CommonConstant.AFFILIATE_LINK + affiliate.getId()); + } else { + affiliate.setStatus("Refused"); + affiliate.setApproved(false); + } + affiliate.setUpdateTime(LocalDateTime.now()); + baseMapper.updateById(affiliate); + + // 2、将批准结果邮件通知用户 + Account account = accountService.getById(affiliate.getAccountId()); + String[] userEmail = {account.getUserEmail()}; + String userName = account.getUserName(); + if (isApproved){ + SendEmailUtil.affiliateEmailReminder(userEmail, new AffiliateEmailParamsDTO(userName), "accepted"); + }else { + SendEmailUtil.affiliateEmailReminder(userEmail, new AffiliateEmailParamsDTO(userName), "refused"); + } + return true; + } + + // 定时计算佣金 + public void updateAffiliateInfoWithPayment(){ + // id存redis + String lastTime = redisUtil.getFromString(RedisUtil.PAYMENT_INFO_LAST_SCAN_TIME); + String currentTime = LocalDateTime.now().toString(); + // 1、查上次更新之后有无新订单 + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (!StringUtil.isNullOrEmpty(lastTime)){ + queryWrapper.gt("create_time", lastTime); + } + queryWrapper.eq("type","new").eq("trade_state", "paid"); + + List paymentInfos = paymentInfoService.getBaseMapper().selectList(queryWrapper); + if (!paymentInfos.isEmpty()){ + paymentInfos.forEach(paymentInfo -> { + // 2、根据order_no查付款用户id + OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(paymentInfo.getOrderNo()); + if (Objects.isNull(orderInfo)){ + return; + } + Long accountId = orderInfo.getAccountId(); + // 3、查该用户之前是否有初次订阅的订单 + QueryWrapper qwOrderInfo = new QueryWrapper<>(); + qwOrderInfo.eq("account_id", accountId).eq("is_first_subscription", 1); + List orderInfos = orderInfoService.getBaseMapper().selectList(qwOrderInfo); + // 该用户首次订阅(非首次订阅,不分配佣金) + if (orderInfos.isEmpty()){ + // 查询是否绑定affiliateId + Account account = accountService.getById(accountId); + if (!Objects.isNull(account.getInvitationCode())){ + log.info("结算订单id为{}的佣金", orderInfo.getId()); + // 3、若有, 直接更新affiliate的所得 + Affiliate affiliate = baseMapper.selectById(account.getInvitationCode()); + Float payerTotal = paymentInfo.getPayerTotal(); + + if (payerTotal > 0){ + // 分配新用户首次订阅所付费用的25%作为佣金 + BigDecimal commission = BigDecimal.valueOf(payerTotal).multiply(new BigDecimal("0.25")); + BigDecimal monthlyEarning = BigDecimal.valueOf(affiliate.getMonthlyEarnings()).add(commission); + BigDecimal unpaidEarnings = BigDecimal.valueOf(affiliate.getUnpaidEarnings()).add(commission); + int visits = affiliate.getVisits() + 1; + affiliate.setMonthlyEarnings(monthlyEarning.floatValue()); + affiliate.setUnpaidEarnings(unpaidEarnings.floatValue()); + affiliate.setVisits(visits); + affiliate.setUpdateTime(LocalDateTime.now()); + baseMapper.updateById(affiliate); + + orderInfo.setIsCommissionCalculated((byte)1); + + // 4、添加到t_affiliate_income + AffiliateIncome affiliateIncome = new AffiliateIncome(); + affiliateIncome.setAffiliateId(affiliate.getId()); + affiliateIncome.setAffiliateAccountId(affiliate.getAccountId()); + affiliateIncome.setInviteeAccountId(accountId); + affiliateIncome.setAmount(payerTotal); + affiliateIncome.setPaymentInfoId(paymentInfo.getId()); + affiliateIncome.setPaymentTime(paymentInfo.getCreateTime()); + affiliateIncome.setCommission(commission.floatValue()); + affiliateIncome.setCreateTime(LocalDateTime.now()); + affiliateIncomeMapper.insert(affiliateIncome); + } + } + orderInfo.setIsFirstSubscription((byte)1); + orderInfo.setUpdateTime(LocalDateTime.now()); + orderInfoService.updateById(orderInfo); + } + }); + } + redisUtil.addToString(RedisUtil.PAYMENT_INFO_LAST_SCAN_TIME, currentTime); + } + + public Boolean affiliateLinkViewsIncrease(Long affiliateId) { + redisUtil.increaseAffiliateLinkViewCount(affiliateId); + return Boolean.TRUE; + } + + private Long getAffiliateLinkViewCount(Long affiliateId) { + return redisUtil.getAffiliateLinkViewCount(affiliateId); + } + + // 查看每个affiliate带来收入的详情 + @Override + public IPage getEachAffiliateGeneratedRevenue(AffiliateQueryDTO affiliateQueryDTO) { + if (Objects.isNull(affiliateQueryDTO.getAffiliateId())){ + throw new BusinessException("Please specify the affiliate ID.", ResultEnum.PROMPT.getCode()); + } + + QueryWrapper affiliateIncomeQueryWrapper = new QueryWrapper<>(); + affiliateIncomeQueryWrapper + .gt(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getStartTime()), "create_time", affiliateQueryDTO.getStartTime()) + .lt(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getEndTime()), "create_time", affiliateQueryDTO.getEndTime()) + .eq(!Objects.isNull(affiliateQueryDTO.getAffiliateId()), "affiliate_id", affiliateQueryDTO.getAffiliateId()) + .orderByDesc(affiliateQueryDTO.getOrder().equals("DESC"), "create_time"); + IPage affiliateIncomePage = affiliateIncomeMapper.selectPage(new Page<>(affiliateQueryDTO.getPage(), affiliateQueryDTO.getSize()), affiliateIncomeQueryWrapper); + return affiliateIncomePage.convert((Function) affiliateIncome -> { + AffiliateInvitationDetailsVO affiliateInvitationDetailsVO = CopyUtil.copyObject(affiliateIncome, AffiliateInvitationDetailsVO.class); + affiliateInvitationDetailsVO.setAccountId(affiliateIncome.getInviteeAccountId()); + affiliateInvitationDetailsVO.setUsername(accountService.getBaseMapper().selectById(affiliateIncome.getInviteeAccountId()).getUserName()); + affiliateInvitationDetailsVO.setFirstSubscriptionPaymentAmount(affiliateIncome.getAmount()); + affiliateInvitationDetailsVO.setCommission(affiliateIncome.getCommission()); + affiliateInvitationDetailsVO.setTime(affiliateIncome.getPaymentTime()); + return affiliateInvitationDetailsVO; + }); + } + + public void commissionCalculation(Integer year, Integer month) { + if (Objects.isNull(year)) { + year = LocalDateTime.now().getYear(); + } + if (Objects.isNull(month)) { + month = LocalDateTime.now().getMonthValue(); + } + + List> monthlyAffiliateIncome = affiliateIncomeMapper.getMonthlyAffiliateIncome(year, month); + // 1、总收入(近一个月通过affiliate产生的收入),未支付的金额 affiliate表中unpaid的总和 + Double totalAmount = 0.0; + Double totalCommission = 0.0; + if (!monthlyAffiliateIncome.isEmpty()){ + Map monthlyIncome = monthlyAffiliateIncome.get(0); + totalAmount = (Double) monthlyIncome.get("totalAmount"); + totalCommission = (Double) monthlyIncome.get("totalCommission"); + } + + // 2、本月新注册的Affiliate + Map monthlyApprovedAffiliate = baseMapper.getMonthlyApprovedAffiliate(year, month); + Long count = monthlyApprovedAffiliate.get("count"); + + AffiliateEmailParamsDTO affiliateEmailParamsDTO = new AffiliateEmailParamsDTO(); + affiliateEmailParamsDTO.setTotalProgramRevenue(totalAmount.toString()); + affiliateEmailParamsDTO.setNewApprovedAffiliates(count.toString()); + affiliateEmailParamsDTO.setUnpaidEarnings(totalCommission.toString()); + affiliateEmailParamsDTO.setPaidEarnings("0"); + +// String merchantEmail = "kimwong@code-create.com.hk"; + String developer = "xupei3360@163.com"; + String[] receiverEmail = {/*merchantEmail, */developer}; + // 邮件通知 + SendEmailUtil.affiliateEmailReminder(receiverEmail, affiliateEmailParamsDTO, "summary"); + } + + @Override + public Affiliate getByAccountId(Long accountId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("account_id", accountId).orderByDesc("id").last("limit 1"); + + return baseMapper.selectOne(queryWrapper); + } + + +} 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 ccbd47d2..2d3e6ed4 100644 --- a/src/main/java/com/ai/da/service/impl/AliPayServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/AliPayServiceImpl.java @@ -7,6 +7,7 @@ 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.model.dto.ProductPurchaseDTO; import com.ai.da.service.*; import com.alibaba.fastjson.JSONObject; import com.alipay.api.AlipayApiException; @@ -23,6 +24,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; @@ -54,21 +56,21 @@ public class AliPayServiceImpl implements AliPayService { @Transactional(rollbackFor = Exception.class) @Override - public String tradeCreate(Integer amount, String returnUrl) { + public String tradeCreate(ProductPurchaseDTO productPurchaseDTO, HttpServletRequest request) { try { //生成订单 log.info("生成订单"); - OrderInfo orderInfo = orderInfoService.createOrderByProductId(amount, PayTypeEnum.ALIPAY.getType()); + OrderInfo orderInfo = orderInfoService.createOrderByProductId(productPurchaseDTO.getQuantity(), PayTypeEnum.ALIPAY.getType(), request); //调用支付宝接口 - AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); + AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest(); //配置需要的公共请求参数 //支付完成后,支付宝发起异步通知的地址 - request.setNotifyUrl(config.getProperty("alipay.notify-url")); + alipayRequest.setNotifyUrl(config.getProperty("alipay.notify-url")); //支付完成后,我们想让页面跳转回aida的页面,配置returnUrl -// request.setReturnUrl(config.getProperty("alipay.return-url")); - request.setReturnUrl(returnUrl); +// alipayRequest.setReturnUrl(config.getProperty("alipay.return-url")); + alipayRequest.setReturnUrl(productPurchaseDTO.getReturnUrl()); //组装当前业务方法的请求参数 JSONObject bizContent = new JSONObject(); @@ -79,10 +81,10 @@ public class AliPayServiceImpl implements AliPayService { bizContent.put("subject", "积分购买"); bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY"); - request.setBizContent(bizContent.toString()); + alipayRequest.setBizContent(bizContent.toString()); //执行请求,调用支付宝接口 - AlipayTradePagePayResponse response = alipayClient.pageExecute(request); + AlipayTradePagePayResponse response = alipayClient.pageExecute(alipayRequest); if(response.isSuccess()){ log.info("调用成功,返回结果 ===> " + response.getBody()); @@ -209,7 +211,7 @@ public class AliPayServiceImpl implements AliPayService { //更新订单状态 orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.SUCCESS); //记录支付日志 - paymentInfoService.createPaymentInfoForAliPay(params); + paymentInfoService.createPaymentInfoForAliPay(params,"credits"); float quantity = Float.parseFloat(totalAmount) / Float.parseFloat(CreditsEventsEnum.PRICE.getValue()); // 更新积分 creditsService.buyCredits(orderByOrderNo.getAccountId(), quantity); @@ -217,7 +219,8 @@ public class AliPayServiceImpl implements AliPayService { creditsService.insertToCreditsDetail(orderByOrderNo.getAccountId(), CreditsEventsEnum.BUY_CREDITS.getName() + "--Alipay", String.valueOf((Long.parseLong(CreditsEventsEnum.BUY_CREDITS.getValue()) * quantity)), - "positive"); + "positive", + orderByOrderNo.getOrderNo()); } finally { //要主动释放锁 lock.unlock(); @@ -312,7 +315,7 @@ public class AliPayServiceImpl implements AliPayService { //如果订单已支付,则更新商户端订单状态 orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.SUCCESS); //并记录支付日志 - paymentInfoService.createPaymentInfoForAliPay(alipayTradeQueryResponse); + paymentInfoService.createPaymentInfoForAliPay(alipayTradeQueryResponse, "credits"); float quantity = orderByOrderNo.getTotalFee() / Float.parseFloat(CreditsEventsEnum.PRICE.getValue()); // 更新积分 creditsService.buyCredits(orderByOrderNo.getAccountId(), quantity); @@ -320,7 +323,8 @@ public class AliPayServiceImpl implements AliPayService { creditsService.insertToCreditsDetail(orderByOrderNo.getAccountId(), CreditsEventsEnum.BUY_CREDITS.getName() + "--Alipay", String.valueOf((Long.parseLong(CreditsEventsEnum.BUY_CREDITS.getValue()) * quantity)), - "positive"); + "positive", + orderByOrderNo.getOrderNo()); } } @@ -393,7 +397,7 @@ public class AliPayServiceImpl implements AliPayService { // 更新积分状态 OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderNo); // creditsService.creditsRefund(orderByOrderNo.getAccountId(), orderByOrderNo.getTotalFee() / Integer.parseInt(CreditsEventsEnum.PRICE.getValue())); - creditsService.creditsRefund(orderByOrderNo.getAccountId(), (int)(orderByOrderNo.getTotalFee() / Float.parseFloat(CreditsEventsEnum.PRICE.getValue()))); + creditsService.creditsRefund(orderByOrderNo.getAccountId(), (int)(orderByOrderNo.getTotalFee() / Float.parseFloat(CreditsEventsEnum.PRICE.getValue())), orderNo); } else { log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg()); diff --git a/src/main/java/com/ai/da/service/impl/AlipayHKServiceImpl.java b/src/main/java/com/ai/da/service/impl/AlipayHKServiceImpl.java index f20d7350..618d67ff 100644 --- a/src/main/java/com/ai/da/service/impl/AlipayHKServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/AlipayHKServiceImpl.java @@ -6,9 +6,12 @@ import com.ai.da.common.enums.OrderStatusEnum; import com.ai.da.common.enums.PayTypeEnum; import com.ai.da.common.utils.AlipayHKEncryptionUtil; import com.ai.da.common.utils.AlipayHKRequestUtil; +import com.ai.da.common.utils.SendEmailUtil; +import com.ai.da.mapper.primary.AccountMapper; import com.ai.da.mapper.primary.entity.OrderInfo; import com.ai.da.model.dto.AlipayHKCallbackDTO; import com.ai.da.model.dto.AlipayHKRequestDTO; +import com.ai.da.model.dto.ProductPurchaseDTO; import com.ai.da.service.*; import com.alibaba.fastjson.JSONObject; @@ -18,6 +21,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.HashMap; @@ -54,29 +58,31 @@ public class AlipayHKServiceImpl implements AlipayHKService { private AlipayHKEncryptionUtil alipayHKEncryptionUtil; @Resource private AlipayHKRequestUtil alipayHKRequestUtil; + @Resource + private AccountMapper accountMapper; /** * 创建订单 */ @Override - public String createOrder(Integer amount, String wallet){ + public String createOrder(ProductPurchaseDTO productPurchaseDTO, HttpServletRequest request){ try{ HashMap param = new HashMap<>(); String orderRef = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); param.put("order_ref", orderRef); - param.put("amount", Float.parseFloat(CreditsEventsEnum.PRICE.getValue()) * amount); + param.put("amount", Float.parseFloat(CreditsEventsEnum.PRICE.getValue()) * productPurchaseDTO.getQuantity()); param.put("subject", "AiDA Credits Purchase"); // ALIPAYHK 或者 ALIPAYCN - param.put("wallet", wallet); + param.put("wallet", productPurchaseDTO.getWallet()); param.put("segment_id", segmentId); // param.put("payment_solution", "WAP"); param.put("payment_solution", "PC2MOBILE"); log.info("alipay-hk 创建订单,参数信息: {}", param); // 生成订单 log.info("创建订单"); - OrderInfo orderInfo = orderInfoService.createOrderByProductId(amount, PayTypeEnum.ALIPAY_HK.getType()); + OrderInfo orderInfo = orderInfoService.createOrderByProductId(productPurchaseDTO.getQuantity(), PayTypeEnum.ALIPAY_HK.getType(), request); /*// 加密 AlipayHKRequestDTO alipayHKRequestDTO = alipayHKEncryptionUtil.AESCBCWithRSA(param, AlipayHKConstant.CREATE_ORDER); // 请求Alipay服务端 @@ -239,7 +245,7 @@ public class AlipayHKServiceImpl implements AlipayHKService { orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.SUCCESS); log.info("Alipay-HK 订单:{} 状态更新成功",orderNo); //记录支付日志 - paymentInfoService.createPaymentInfoForAliPayHK(alipayHKCallbackDTO); + paymentInfoService.createPaymentInfoForAliPayHK(alipayHKCallbackDTO, "credits"); log.info("Alipay-HK 订单:{} 支付信息状态更新成功",orderNo); float quantity = Float.parseFloat(totalAmount) / Float.parseFloat(CreditsEventsEnum.PRICE.getValue()); // 更新积分 @@ -248,8 +254,12 @@ public class AlipayHKServiceImpl implements AlipayHKService { creditsService.insertToCreditsDetail(orderByOrderNo.getAccountId(), CreditsEventsEnum.BUY_CREDITS.getName() + "--AlipayHK", String.valueOf((Long.parseLong(CreditsEventsEnum.BUY_CREDITS.getValue()) * quantity)), - "positive"); + "positive", + orderByOrderNo.getOrderNo()); log.info("用户:{} 积分信息更新成功",orderByOrderNo.getAccountId()); + // 填入用户信息,邮件通知Kim + String username = accountMapper.selectById(orderByOrderNo.getAccountId()).getUserName(); + SendEmailUtil.creditsPurchaseReminder(username, String.valueOf(quantity), totalAmount); } finally { //要主动释放锁 lock.unlock(); diff --git a/src/main/java/com/ai/da/service/impl/CollectionElementServiceImpl.java b/src/main/java/com/ai/da/service/impl/CollectionElementServiceImpl.java index a6bcedc6..73f0ec99 100644 --- a/src/main/java/com/ai/da/service/impl/CollectionElementServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/CollectionElementServiceImpl.java @@ -216,6 +216,8 @@ public class CollectionElementServiceImpl extends ServiceImpl qw = new QueryWrapper<>(); + qw.lambda().eq(MoodboardPosition::getCollectionId, id); + List moodboardPositions = moodboardPositionMapper.selectList(qw); + if (moodboardPositions != null && !moodboardPositions.isEmpty()) { + // 将查询结果转换为 JSON 字符串 + JSONObject resultJson = new JSONObject(); + + // 遍历 MoodboardPosition 列表,根据实际情况填充到 resultJson 中 + for (MoodboardPosition position : moodboardPositions) { + String type = position.getType(); + String styleData = position.getStyleData(); + int sequence = position.getSequence(); // 获取 sequence 值 + + // 如果 type 字段为 "class",直接将其作为字符串处理 + if ("class".equals(type)) { + // 如果 type 字段还没有对应的 JSONArray,则初始化它 + if (!resultJson.containsKey(type)) { + resultJson.put(type, new ArrayList<>()); + } + + // 获取对应类型的列表 + List classList = (List) resultJson.get(type); + + // 确保列表长度足够,可以容纳 `sequence` 索引 + while (classList.size() <= sequence) { + classList.add(""); // 添加空字符串,直到长度大于 `sequence` + } + + // 将 class 字段直接存入列表 + classList.set(sequence, styleData); // 根据 sequence 设置数据 + + resultJson.put(type, classList); + } else { + // 如果 type 字段为其他类型,按照正常方式处理 + if (!resultJson.containsKey(type)) { + resultJson.put(type, new ArrayList<>()); + } + + // 获取对应类型的列表 + List styleList = (List) resultJson.get(type); + + // 确保列表长度足够,可以容纳 `sequence` 索引 + while (styleList.size() <= sequence) { + styleList.add(new JSONObject()); // 添加空的 JSONObject,直到长度大于 `sequence` + } + + // 将解析的样式数据存入正确的索引位置 + JSONObject styleObject = JSON.parseObject(styleData); + styleList.set(sequence, styleObject); // 根据 sequence 设置数据 + + resultJson.put(type, styleList); + } + } + + // 将最终结果转换为字符串 + return resultJson.toJSONString(); + } else { + return null; // 返回空的 JSON 对象 + } + } + private List resolveColorBoard(List collectionElements) { return CopyUtil.copyList(collectionElements, CollectionColorVO.class, (o, d) -> { String name = o.getName(); diff --git a/src/main/java/com/ai/da/service/impl/ConvenientInquiryServiceImpl.java b/src/main/java/com/ai/da/service/impl/ConvenientInquiryServiceImpl.java index 69167b80..fb24c7ef 100644 --- a/src/main/java/com/ai/da/service/impl/ConvenientInquiryServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/ConvenientInquiryServiceImpl.java @@ -4,19 +4,18 @@ import com.ai.da.common.config.exception.BusinessException; import com.ai.da.common.constant.CommonConstant; import com.ai.da.common.context.UserContext; import com.ai.da.common.enums.CreditsEventsEnum; +import com.ai.da.common.response.PageBaseResponse; import com.ai.da.common.utils.CopyUtil; -import com.ai.da.mapper.primary.AccountMapper; -import com.ai.da.mapper.primary.QuestionnaireMapper; -import com.ai.da.mapper.primary.ToProductImageResultMapper; -import com.ai.da.mapper.primary.TrialOrderMapper; +import com.ai.da.common.utils.DateUtil; +import com.ai.da.common.utils.MinioUtil; +import com.ai.da.mapper.primary.*; import com.ai.da.mapper.primary.entity.Account; import com.ai.da.mapper.primary.entity.Questionnaire; import com.ai.da.mapper.primary.entity.TrialOrder; import com.ai.da.model.dto.AccountAddDTO; +import com.ai.da.model.dto.QueryPaymentInfoDTO; import com.ai.da.model.enums.Language; -import com.ai.da.model.vo.QuestionnaireFeedbackVO; -import com.ai.da.model.vo.QuestionnaireVO; -import com.ai.da.model.vo.QueryUserConditionsVO; +import com.ai.da.model.vo.*; import com.ai.da.service.*; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; @@ -24,14 +23,26 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.mysql.cj.util.StringUtils; +import io.minio.GetPresignedObjectUrlArgs; +import io.minio.MinioClient; +import io.minio.PutObjectArgs; +import io.minio.errors.*; +import io.minio.http.Method; import io.netty.util.internal.StringUtil; import lombok.extern.slf4j.Slf4j; +import org.apache.poi.xssf.usermodel.*; +import org.springframework.beans.factory.annotation.Autowired; +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.http.HttpServletResponse; +import java.io.*; import java.math.BigDecimal; import java.math.RoundingMode; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; @@ -43,9 +54,25 @@ public class ConvenientInquiryServiceImpl extends ServiceImpl getTrial(QueryUserConditionsVO queryUserConditionsVO) { log.info("getTrial parameter : {},page:{}, size:{}", queryUserConditionsVO, queryUserConditionsVO.getPage(), queryUserConditionsVO.getSize()); @@ -211,45 +238,6 @@ public class ConvenientInquiryServiceImpl extends ServiceImpl> getActiveUserFunc(String startTime, String endTime, List ids) { log.info("getActiveUserFunc ==> startTime:{}, endTime:{}, accountList:{}", startTime, endTime, ids); @@ -388,9 +365,6 @@ public class ConvenientInquiryServiceImpl extends ServiceImpl conversionRate(String startTime, String endTime) { @@ -457,7 +431,6 @@ public class ConvenientInquiryServiceImpl extends ServiceImpl (Long)map.get("id")).collect(Collectors.toList()); } + /** + * 查询交易记录 + * 允许按日期,支付方式,支付金额,商品种类,交易状态,付款人的国家或城市查询,需要分页查询 + */ + public PageBaseResponse queryTransactionRecords(QueryPaymentInfoDTO queryPaymentInfoDTO) { + Integer size = queryPaymentInfoDTO.getSize(); + int offset = (queryPaymentInfoDTO.getPage() - 1) * size; + String order = "DESC"; + if (!StringUtil.isNullOrEmpty(queryPaymentInfoDTO.getOrder()) && queryPaymentInfoDTO.getOrder().equals("ASC")) { + order = "ASC"; + } + List paymentInfoVOS = paymentInfoMapper.queryPaymentInfo(queryPaymentInfoDTO.getPlatform(), queryPaymentInfoDTO.getPayerTotal(), + queryPaymentInfoDTO.getType(), queryPaymentInfoDTO.getStatus(), + queryPaymentInfoDTO.getCountry(), queryPaymentInfoDTO.getCity(), + queryPaymentInfoDTO.getStartTime(), queryPaymentInfoDTO.getEndTime(), + size, offset, order, queryPaymentInfoDTO.getPayer()); + // 查询数据总量 + Long total = paymentInfoMapper.queryPaymentInfoCount(queryPaymentInfoDTO.getPlatform(), queryPaymentInfoDTO.getPayerTotal(), + queryPaymentInfoDTO.getType(), queryPaymentInfoDTO.getStatus(), + queryPaymentInfoDTO.getCountry(), queryPaymentInfoDTO.getCity(), + queryPaymentInfoDTO.getStartTime(), queryPaymentInfoDTO.getEndTime(), queryPaymentInfoDTO.getPayer()); + + // 总页数 + double totalPage = Math.ceil((double) total / size); + // 组装返回参数 + PageBaseResponse response = new PageBaseResponse<>(); + response.setContent(paymentInfoVOS); + response.setPage(queryPaymentInfoDTO.getPage()); + response.setSize(size); + response.setTotal(total); + response.setPages((long) totalPage); + return response; + } + + public Map> getCities(){ + List> cities = paymentInfoMapper.getCities(); + List> countries = paymentInfoMapper.getCountries(); + List cityCollect = cities.stream() + .map(cityEntry -> cityEntry.get("city")) + .collect(Collectors.toList()); + List countryCollect = countries.stream() + .map(cityEntry -> cityEntry.get("country")) + .collect(Collectors.toList()); + + return new HashMap>() {{ + put("city", cityCollect); + put("country", countryCollect); + }}; + } + + public String exportTransactionRecords(QueryPaymentInfoDTO queryPaymentInfoDTO, HttpServletResponse response){ +// QueryPaymentInfoDTO queryPaymentInfoDTO = JSONObject.parseObject(params, QueryPaymentInfoDTO.class); + + // 查询数据总量 + Long total = paymentInfoMapper.queryPaymentInfoCount(queryPaymentInfoDTO.getPlatform(), queryPaymentInfoDTO.getPayerTotal(), + queryPaymentInfoDTO.getType(), queryPaymentInfoDTO.getStatus(), + queryPaymentInfoDTO.getCountry(), queryPaymentInfoDTO.getCity(), + queryPaymentInfoDTO.getStartTime(), queryPaymentInfoDTO.getEndTime(), queryPaymentInfoDTO.getPayer()); + List paymentInfoVOS = paymentInfoMapper.queryPaymentInfo(queryPaymentInfoDTO.getPlatform(), queryPaymentInfoDTO.getPayerTotal(), + queryPaymentInfoDTO.getType(), queryPaymentInfoDTO.getStatus(), + queryPaymentInfoDTO.getCountry(), queryPaymentInfoDTO.getCity(), + queryPaymentInfoDTO.getStartTime(), queryPaymentInfoDTO.getEndTime(), + total.intValue(), 0, "ASC", queryPaymentInfoDTO.getPayer()); + try { + return excelExport(paymentInfoVOS, response); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Resource + private MinioClient minioClient; + + public String excelExport(List paymentInfoVOS, HttpServletResponse response) throws IOException { + if (paymentInfoVOS == null || paymentInfoVOS.isEmpty()) { + log.info("无数据,直接返回,不生成空文件"); + return null; + } + + // 文件名称 + String time = DateUtil.dateToStr(new Date(), DateUtil.YYYY_MM_DD_hh_mm_ss); + String fileName = "paymentInfo_" + time + ".xlsx"; + + // 创建Excel文件 + try (XSSFWorkbook workBook = new XSSFWorkbook()) { + // 创建Excel工作表(Table),之后的数据都添加到该表中 + XSSFSheet sheet = workBook.createSheet("数据导出"); + // 设置列宽 + sheet.setDefaultColumnWidth(16); + + // 创建标题行 + XSSFRow titleRow = sheet.createRow(0); + String[] title = new String[]{"id", "payer", "platform", "payerTotal", "type", "status", + "country", "city", "paymentMethod", "last4", "createTime"}; + //设置标题字体样式 + XSSFCellStyle cellStyle = workBook.createCellStyle(); + XSSFFont font = workBook.createFont(); + font.setBold(true);//加粗 + font.setFontHeightInPoints((short) 14);//设置字体大小 + cellStyle.setFont(font); + //设置标题列 + for (int i = 0; i < title.length; i++) { + //创建标题的单元格 + XSSFCell titleCell = titleRow.createCell(i); + //填充标题数值 + titleCell.setCellValue(title[i]); + //设置样式 + titleCell.setCellStyle(cellStyle); + } + + // 填充数据 + //第一行是标题所以要从第二行开始 + for (int i = 0; i < paymentInfoVOS.size(); i++) { + PaymentInfoVO paymentInfoVO = paymentInfoVOS.get(i); + XSSFRow row = sheet.createRow(i + 1); + for (int j = 0; j < title.length; j++) { + XSSFCell cell = row.createCell(j); + String exportKey = title[j]; + switch (exportKey) { + case "id": + cell.setCellValue(paymentInfoVO.getId()); + break; + case "payer": + cell.setCellValue(paymentInfoVO.getPayer()); + break; + case "platform": + cell.setCellValue(paymentInfoVO.getPlatform()); + break; + case "payerTotal": + cell.setCellValue(paymentInfoVO.getPayerTotal()); + break; + case "type": + cell.setCellValue(paymentInfoVO.getType()); + break; + case "status": + cell.setCellValue(paymentInfoVO.getStatus()); + break; + case "country": + cell.setCellValue(paymentInfoVO.getCountry()); + break; + case "city": + cell.setCellValue(paymentInfoVO.getCity()); + break; + case "paymentMethod": + cell.setCellValue(paymentInfoVO.getPaymentMethod()); + break; + case "last4": + cell.setCellValue(paymentInfoVO.getLast4()); + break; + case "createTime": + cell.setCellValue(paymentInfoVO.getCreateTime()); + break; + } + } + } + + // 将workBook转换为字节数组 直接以文件流的形式返回 + /*try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { + workBook.write(byteArrayOutputStream); + byte[] bytes = byteArrayOutputStream.toByteArray(); + + // 设置响应头 + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8")); + + // 将字节数组写入响应流 + try (OutputStream responseOut = response.getOutputStream()) { + responseOut.write(bytes); + responseOut.flush(); + } + }*/ + + String filePath = UserContext.getUserHolder().getId() + "/transactionDownload" + "/" + fileName; + // 将workBook转换为字节数组 + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + workBook.write(byteArrayOutputStream); + byte[] bytes = byteArrayOutputStream.toByteArray(); + + // 上传到 MinIO + try (InputStream inputStream = new ByteArrayInputStream(bytes)) { + try { + minioClient.putObject(PutObjectArgs.builder() + .bucket(userBucket) + .object(filePath) + .stream(inputStream, bytes.length, -1) + .contentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + .build()); + } catch (InvalidKeyException | NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } catch (MinioException e) { + log.error("文件上传失败: " + e); + throw new RuntimeException(e); + } + + // 获取下载链接 + String downloadUrl = null; + try { + downloadUrl = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() + .bucket(userBucket) + .object(filePath) + .method(Method.GET) + .expiry(60 * 60 * 24) // 设置链接有效期为24小时 + .build()); + } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | + InvalidResponseException | NoSuchAlgorithmException | XmlParserException | ServerException e) { + throw new RuntimeException(e); + } + + return downloadUrl; // 返回下载链接 + } + } } diff --git a/src/main/java/com/ai/da/service/impl/CreditsServiceImpl.java b/src/main/java/com/ai/da/service/impl/CreditsServiceImpl.java index 9cc17ce8..5c24211c 100644 --- a/src/main/java/com/ai/da/service/impl/CreditsServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/CreditsServiceImpl.java @@ -100,7 +100,7 @@ public class CreditsServiceImpl extends ServiceImpl增 negative->减 */ @Override - public void insertToCreditsDetail(Long accountId, String changeEvent, String credits, String changeType) { + public void insertToCreditsDetail(Long accountId, String changeEvent, String credits, String changeType, String orderNo) { CreditsDetail creditsDetail = new CreditsDetail(); Account account = accountMapper.selectById(accountId); // BigDecimal finalCredits; @@ -137,6 +137,7 @@ public class CreditsServiceImpl extends ServiceImpl impleme @Resource private TDesignPythonOutfitMapper designPythonOutfitMapper; + @Resource + private MoodboardPositionMapper moodboardPositionMapper; + @Value("${minio.endpoint}") private String endpoint; @Value("${minio.bucketName.results}") @@ -109,8 +111,17 @@ public class DesignServiceImpl extends ServiceImpl impleme @Resource private RedisUtil redisUtil; + @Resource + private DesignBatchMapper designBatchMapper; + + @Resource + private UserLikeSortMapper userLikeSortMapper; + + private final ConcurrentHashMap> designContext = new ConcurrentHashMap<>(); + + @Override - public DesignCollectionVO designCollection(DesignCollectionDTO designDTO) { + public String designCollection(DesignCollectionDTO designDTO) { AuthPrincipalVo userInfo = UserContext.getUserHolder(); //校验collection element ValidateElementVO elementVO = collectionElementService.validateElement(designDTO); @@ -283,8 +294,8 @@ public class DesignServiceImpl extends ServiceImpl impleme // return saveDesignItemAndDetail(pythonObjects, designId, collectionId, userInfo, designDTO.getTimeZone()); // } - private DesignCollectionVO designOrRedesignOperateNew(DesignCollectionDTO designDTO, AuthPrincipalVo userInfo, - Long collectionIdParam, ValidateElementVO elementVO) { + private String designOrRedesignOperateNew(DesignCollectionDTO designDTO, AuthPrincipalVo userInfo, + Long collectionIdParam, ValidateElementVO elementVO) { if (CollectionUtil.isNotEmpty(designDTO.getSketchBoards())) { //编辑sketchBoard collectionElementService.editSketchBoardsElement(elementVO, designDTO.getSketchBoards()); @@ -297,16 +308,10 @@ public class DesignServiceImpl extends ServiceImpl impleme Long collectionId; if (null == collectionIdParam) { collectionId = collectionService.saveCollection(userInfo.getId(), designDTO.getTimeZone(), designDTO.getMoodTemplateId(), designDTO.getMoodboardPosition()); + String moodboardPosition = designDTO.getMoodboardPosition(); + parseMoodboardPosition(moodboardPosition, collectionId); }else { collectionId = collectionIdParam; -// Collection byId = collectionService.getById(collectionIdParam); -// if (!designDTO.getMoodboardPostion().equals(byId.getMoodboardPosition())) { -// byId.setMoodboardPosition(designDTO.getMoodboardPostion()); -// } -// if (!designDTO.getMoodTemplateId().equals(byId.getMoodTemplateId())) { -// byId.setMoodTemplateId(designDTO.getMoodTemplateId()); -// } -// collectionService.updateById(byId); } List elementIds = getElementId(elementVO); //批量关联element 到 collection @@ -336,7 +341,9 @@ public class DesignServiceImpl extends ServiceImpl impleme log.info("增加image_id关联运行时间:" + totalTimeInSeconds + " 秒"); //design startTime = System.currentTimeMillis(); - JSONObject responseJSONObject = pythonService.designNew(pythonObjects); + String requestId = UUID.randomUUID().toString(); + pythonObjects.setRequestId(requestId); + JSONObject responseJSONObject = pythonService.designStream(pythonObjects); endTime = System.currentTimeMillis(); totalTimeInSeconds = (endTime - startTime) / 1000; log.info("design python端运行时间:" + totalTimeInSeconds + " 秒"); @@ -350,10 +357,94 @@ public class DesignServiceImpl extends ServiceImpl impleme endTime = System.currentTimeMillis(); totalTimeInSeconds = (endTime - startTime) / 1000; log.info("处理关联关系运行时间:" + totalTimeInSeconds + " 秒"); + + + Map context = new HashMap<>(); + context.put("pythonObjects", pythonObjects); // 转换后的 Python 请求参数 + context.put("designId", designId); // 设计 ID + context.put("collectionId", collectionId); // 集合 ID + context.put("userInfo", userInfo); // 用户信息 + context.put("timeZone", designDTO.getTimeZone()); // 时区 + context.put("singleOverall", designDTO.getSingleOverall()); // 其他设计参数 + context.put("requestIdList", elementVO.getRequestIdList()); + + // 将上下文存入全局设计上下文中 + designContext.put(requestId, context); + //保存python返回信息;保存designItem和detail - return savePythonDesignItemAndDetail(pythonObjects, designId, collectionId, userInfo, designDTO.getTimeZone(), responseJSONObject, designDTO.getSingleOverall()); +// return savePythonDesignItemAndDetail(pythonObjects, designId, collectionId, userInfo, designDTO.getTimeZone(), responseJSONObject, designDTO.getSingleOverall()); + return requestId; } + private void parseMoodboardPosition(String moodboardPosition, Long collectionIdParam) { + if (!StringUtils.isEmpty(moodboardPosition)) { + // 将 JSON 字符串解析为 JSONObject + JSONObject moodboardPositionJson = JSONObject.parseObject(moodboardPosition); + + // 准备保存的 MoodboardPosition 列表 + List moodboardPositions = new ArrayList<>(); + + // 遍历 JSON 对象的 key(即样式类型) + for (String key : moodboardPositionJson.keySet()) { + // 特殊处理 "class" 字段 + if ("class".equals(key)) { + // 获取 "class" 字段的值并将其转为 List + JSONArray classArray = moodboardPositionJson.getJSONArray(key); + if (classArray != null) { + + for (int j = 0; j < classArray.size(); j++) { + // 将 classList 存入 MoodboardPosition(或者其他结构) + + MoodboardPosition position = new MoodboardPosition() + .setCollectionId(collectionIdParam) // 关联 Collection ID + .setType(key) // 样式类型 + .setStyleData(classArray.getString(j)) // 设置 class 字段 + .setSequence(j) // 根据索引值设置顺序 + .setCreateTime(LocalDateTime.now()) // 创建时间 + .setUpdateTime(LocalDateTime.now()); // 更新时间 + + // 添加到列表中 + moodboardPositions.add(position); + } + } + continue; // 跳过 "class" 字段的常规处理 + } + + JSONArray styleArray = moodboardPositionJson.getJSONArray(key); + if (styleArray != null) { + for (int i = 0; i < styleArray.size(); i++) { + // 获取当前样式数据 + JSONObject styleData = styleArray.getJSONObject(i); + + // 构建 MoodboardPosition 实例 + MoodboardPosition position = new MoodboardPosition() + .setCollectionId(collectionIdParam) // 关联 Collection ID + .setType(key) // 样式类型 + .setStyleData(styleData.toJSONString()) // 样式数据存为 JSON 字符串 + .setSequence(i) // 根据索引值设置顺序 + .setCreateTime(LocalDateTime.now()) // 创建时间 + .setUpdateTime(LocalDateTime.now()); // 更新时间 + + // 添加到列表中 + moodboardPositions.add(position); + } + } + } + // 如果解析结果非空,保存到数据库 + if (!moodboardPositions.isEmpty()) { + for (MoodboardPosition position : moodboardPositions) { + moodboardPositionMapper.insert(position); + } + log.info("成功解析并保存 {} 条 MoodboardPosition 数据", moodboardPositions.size()); + } else { + log.warn("未找到可保存的 MoodboardPosition 数据"); + } + } else { + log.warn("传入的 moodboardPosition 字段为空"); + } + } + + @Override public void relationImageId(DesignPythonObjects pythonObjects) { if (Objects.isNull(pythonObjects)) { @@ -601,6 +692,152 @@ public class DesignServiceImpl extends ServiceImpl impleme @Resource private MinioUtil minioUtil; + private DesignCollectionVO savePythonDesignItemAndDetailSingle(DesignPythonObjects pythonObjects, Long designId, Long collectionId, AuthPrincipalVo userInfo, String timeZone, JSONObject outfit, String singleOverall, Map context) { + Object designCollectionVO = context.get("DesignCollectionVO"); + DesignCollectionVO response; + List designCollectionItems = Lists.newArrayList(); + if (null == designCollectionVO) { + response = new DesignCollectionVO(); + response.setDesignId(designId); + response.setCollectionId(collectionId); + response.setProcessId(pythonObjects.getProcess_id()); + }else { + response = (DesignCollectionVO) designCollectionVO; + designCollectionItems = response.getDesignCollectionItems(); + } + + response.setDesignCollectionItems(designCollectionItems); + + + DesignPythonObject item = new DesignPythonObject(); + String objectSign = outfit.getString("objectSign"); +// log.info(objectSign); + for (DesignPythonObject object : pythonObjects.getObjects()) { + if (object.getObjectSign().equals(objectSign)) { + item = object; + continue; + } + } + + + DesignItem designItem = new DesignItem(); + designItem.setAccountId(userInfo.getId()); + designItem.setCollectionId(collectionId); + designItem.setDesignId(designId); + designItem.setCreateDate(DateUtil.getByTimeZone(timeZone)); + //生成的八张图片 + designItem.setDesignUrl(item.getBasic().getSave_name()); + designItem.setHasLike((byte) 0); + //生成designItem + Long designItemId = designItemService.saveOne(designItem); + // python design返回入库及封装 +// JSONObject outfit = data.getJSONObject(i + ""); +// if (null == outfit) { +// continue; +// } + TDesignPythonOutfit designPythonOutfit = new TDesignPythonOutfit(); + designPythonOutfit.setDesignItemId(designItemId); + designPythonOutfit.setUserId(userInfo.getId()); + designPythonOutfit.setDesignId(designId); + designPythonOutfit.setCollectionId(collectionId); + String synthesisUrl = outfit.getString("synthesis_url"); + if (!StringUtils.isEmpty(synthesisUrl)) { + designPythonOutfit.setDesignUrl(synthesisUrl); + } else { + throw new BusinessException("design.interface.exception"); + } + designPythonOutfitService.save(designPythonOutfit); + + JSONArray layers = outfit.getJSONArray("layers"); + List list = new ArrayList<>(); + DesignCollectionItemVO designCollectionItemVO = new DesignCollectionItemVO(); + designCollectionItemVO.setObjectSign(objectSign); + for (int i1 = 0; i1 < layers.size(); i1++) { + JSONObject jsonObject = layers.getJSONObject(i1); + TDesignPythonOutfitDetail designPythonOutfitDetail = new TDesignPythonOutfitDetail(); + designPythonOutfitDetail.setDesignId(designId); + designPythonOutfitDetail.setDesignPythonOutfitId(designPythonOutfit.getId()); + designPythonOutfitDetail.setPosition(jsonObject.getString("position")); + designPythonOutfitDetail.setImageSize(jsonObject.getString("image_size")); + designPythonOutfitDetail.setImageUrl(jsonObject.getString("image_url")); + if (singleOverall.equals(SingleOverallEnum.SINGLE.getRealName())) { + designCollectionItemVO.setDesignItemUrl(designItem.getDesignUrl()); + } + designPythonOutfitDetail.setImageCategory(jsonObject.getString("image_category")); + designPythonOutfitDetail.setMaskUrl(jsonObject.getString("mask_url")); + designPythonOutfitDetail.setUserId(userInfo.getId()); + designPythonOutfitDetail.setPriority(Integer.parseInt(jsonObject.getString("priority"))); + designPythonOutfitDetail.setCreateDate(LocalDateTime.now()); + list.add(designPythonOutfitDetail); + } + designPythonOutfitDetailService.saveBatch(list); + designCollectionItemVO.setDesignItemId(designItemId); + designCollectionItemVO.setDesignItemUrl(designItem.getDesignUrl()); + designCollectionItemVO.setDesignOutfitId(designPythonOutfit.getId()); + String designUrl = designPythonOutfit.getDesignUrl(); + if (!StringUtils.isEmpty(designUrl) && designUrl.contains("/")) { + int firstIndex = designUrl.indexOf("/"); + designCollectionItemVO.setDesignOutfitUrl(minioUtil.getPreSignedUrl(designUrl.substring(0, firstIndex) + "/" + designUrl.substring(firstIndex + 1), 24 * 60)); + } + //response + designCollectionItems.add(designCollectionItemVO); + + List designItemDetails = Lists.newArrayList(); + Map typePriority = list.stream().collect(Collectors.toMap(d -> d.getImageCategory().split("_")[0], + d -> Math.abs(d.getPriority()), + (existing, replacement) -> replacement)); + Map typeAndUndividedLayer = designItemService.setTypeAndUndividedLayer(layers); + for (DesignPythonItem detail : item.getItems()) { + if (null == detail) { + continue; + } + DesignItemDetail designItemDetail = CopyUtil.copyObject(detail, DesignItemDetail.class); + designItemDetail.setAccountId(userInfo.getId()); + designItemDetail.setDesignId(designId); + designItemDetail.setDesignItemId(designItemId); + designItemDetail.setCollectionElementId(detail.getElementId()); + designItemDetail.setCreateDate(DateUtil.getByTimeZone(timeZone)); + designItemDetail.setUndividedLayer(typeAndUndividedLayer.get(designItemDetail.getType().toLowerCase())); + if (SysFileLevel2TypeEnum.BODY.getRealName().equals(detail.getType())) { + designItemDetail.setPath(detail.getBody_path()); + //BODY不关联businessId + designItemDetail.setBusinessId(0L); + } + designItemDetail.setIconPath(detail.getIcon()); + designItemDetail.setPriority(typePriority.get(detail.getType().toLowerCase())); + if (!detail.getType().equals("Body")){ + DesignPythonItemPrint printObject = detail.getPrint().getOverall(); +// designItemDetail.setPrintPath(Objects.isNull(printObject) ? "" : printObject.getPath()); + designItemDetail.setPrintPath(CollectionUtils.isEmpty(printObject.getPrint_path_list()) ? "" : printObject.getPrint_path_list().get(0)); + } + designItemDetailService.save(designItemDetail); + if (!SysFileLevel2TypeEnum.BODY.getRealName().equals(detail.getType()) && !StringUtil.isNullOrEmpty(designItemDetail.getPrintPath())) { + DesignItemDetailPrint print = new DesignItemDetailPrint(); + print.setDesignItemDetailId(designItemDetail.getId()); + print.setPrintType("print"); + print.setPath(designItemDetail.getPrintPath()); + print.setSingleOrOverall("overall"); + print.setPosition("[0.0,0.0]"); +// print.setScale(1d); + // todo mark 将print默认scale置为0.3 + print.setScale(0.3d); + print.setAngle(0.0); + print.setPriority(1); + QueryWrapper getPrintboardLevel2TypeQw = new QueryWrapper<>(); + getPrintboardLevel2TypeQw.lambda().eq(CollectionElement::getUrl, print.getPath()); + getPrintboardLevel2TypeQw.lambda().orderByDesc(CollectionElement::getCreateDate); + getPrintboardLevel2TypeQw.last("limit 1"); + CollectionElement one = collectionElementService.getOne(getPrintboardLevel2TypeQw); + print.setLevel2Type(one.getLevel2Type()); + print.setCreateDate(LocalDateTime.now()); + designItemDetailPrintService.save(print); + } + } + synchronized (context) { + context.put("DesignCollectionVO", response); + } + return response; + } private DesignCollectionVO savePythonDesignItemAndDetail(DesignPythonObjects pythonObjects , Long designId, Long collectionId, AuthPrincipalVo userInfo, String timeZone, JSONObject responseJSONObject, String singleOverall) { DesignCollectionVO response = new DesignCollectionVO(); @@ -612,6 +849,7 @@ public class DesignServiceImpl extends ServiceImpl impleme if (data == null) { throw new BusinessException("design.interface.exception"); } + for (int i = 0; i < pythonObjects.getObjects().size(); i++) { DesignPythonObject item = pythonObjects.getObjects().get(i); DesignItem designItem = new DesignItem(); @@ -641,6 +879,7 @@ public class DesignServiceImpl extends ServiceImpl impleme throw new BusinessException("design.interface.exception"); } designPythonOutfitService.save(designPythonOutfit); + JSONArray layers = outfit.getJSONArray("layers"); List list = new ArrayList<>(); DesignCollectionItemVO designCollectionItemVO = new DesignCollectionItemVO(); @@ -750,7 +989,7 @@ public class DesignServiceImpl extends ServiceImpl impleme //生成designItem Long designItemId = designItemService.saveOne(designItem); //response - designCollectionItems.add(new DesignCollectionItemVO(designItemId, designItem.getDesignUrl(), null, null)); + designCollectionItems.add(new DesignCollectionItemVO(designItemId, designItem.getDesignUrl(), null, null, null)); List designItemDetails = Lists.newArrayList(); item.getItems().forEach(detail -> { @@ -797,7 +1036,7 @@ public class DesignServiceImpl extends ServiceImpl impleme } @Override - public DesignCollectionVO reDesignCollection(ReDesignCollectionDTO reDesignDTO) { + public String reDesignCollection(ReDesignCollectionDTO reDesignDTO) { //校验collection Collection collection = collectionService.getById(reDesignDTO.getCollectionId()); if (Objects.isNull(collection)) { @@ -819,7 +1058,7 @@ public class DesignServiceImpl extends ServiceImpl impleme Design oldDesign = selectByCollectionId(reDesignDTO.getCollectionId()); //删除老的关联的design deleteByCollectionId(reDesignDTO.getCollectionId()); - designItemService.deleteByCollectionId(reDesignDTO.getCollectionId()); +// designItemService.deleteByCollectionId(reDesignDTO.getCollectionId()); designItemDetailService.deleteByDesignId(oldDesign.getId()); //redesign return designOrRedesignOperateNew(CopyUtil.copyObject( @@ -849,15 +1088,15 @@ public class DesignServiceImpl extends ServiceImpl impleme } List designItems = designItemService.getByDesignId(designId); if (CollectionUtils.isEmpty(designItems)) { - return new DesignCollectionVO(designId, design.getCollectionId(), null, null); + return new DesignCollectionVO(designId, design.getCollectionId(), null, null, null); } - return new DesignCollectionVO(designId, design.getCollectionId(), coverDesignItemToVO(designItems), null); + return new DesignCollectionVO(designId, design.getCollectionId(), coverDesignItemToVO(designItems), null, null); } private List coverDesignItemToVO(List designItems) { List response = Lists.newArrayList(); designItems.forEach(designItem -> { - response.add(new DesignCollectionItemVO(designItem.getId(), designItem.getDesignUrl(), null, null)); + response.add(new DesignCollectionItemVO(designItem.getId(), designItem.getDesignUrl(), null, null, null)); }); return response; } @@ -874,7 +1113,9 @@ public class DesignServiceImpl extends ServiceImpl impleme } String pictureName = null; UserLike userLike; + Boolean isFirst = true; if (Objects.nonNull(userGroupId)) { + isFirst = false; UserLikeGroup userLikeGroup = userLikeGroupService.getById(userGroupId); if (Objects.isNull(userLikeGroup)) { throw new BusinessException("userLikeGroup.not.found"); @@ -920,6 +1161,35 @@ public class DesignServiceImpl extends ServiceImpl impleme designItem.getDesignId(), designLikeDTO.getDesignItemId(), designLikeDTO.getDesignPythonOutfitId(), tDesignPythonOutfits.get(0).getDesignUrl(), designLikeDTO.getTimeZone()); } userLikeService.save(userLike); + Integer sortParam = 1; + Long userLikeSortId; + if (isFirst) { + UserLikeSort userLikeSort = new UserLikeSort(); + userLikeSort.setUserLikeGroupId(userGroupId); + userLikeSort.setUserLikeId(userLike.getId()); + userLikeSort.setSort(1); + userLikeSortMapper.insert(userLikeSort); + userLikeSortId = userLikeSort.getId(); + }else { + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(UserLikeSort::getUserLikeGroupId, userGroupId); + qw.lambda().orderByDesc(UserLikeSort::getSort); + List userLikeSorts = userLikeSortMapper.selectList(qw); + UserLikeSort userLikeSort = new UserLikeSort(); + if (CollectionUtils.isEmpty(userLikeSorts)) { + userLikeSort.setUserLikeGroupId(userGroupId); + userLikeSort.setUserLikeId(userLike.getId()); + userLikeSort.setSort(1); + }else { + Integer sort = userLikeSorts.get(0).getSort(); + userLikeSort.setUserLikeGroupId(userGroupId); + userLikeSort.setUserLikeId(userLike.getId()); + userLikeSort.setSort(sort + 1); + } + userLikeSortMapper.insert(userLikeSort); + sortParam = userLikeSort.getSort(); + userLikeSortId = userLikeSort.getId(); + } groupDetailId = userLike.getId(); String designUrl = designPythonOutfitMapper.selectById(userLike.getDesignOutfitId()).getDesignUrl(); if (designUrl.contains("/")) { @@ -928,7 +1198,7 @@ public class DesignServiceImpl extends ServiceImpl impleme } //修改designItem为like状态 designItemService.updateLikeStatus(designLikeDTO.getDesignItemId(), (byte) 1); - return new DesignLikeVO(userGroupId, groupDetailId, pictureName); + return new DesignLikeVO(userLikeSortId, userGroupId, groupDetailId, pictureName, userLike.getId(), sortParam); } private List validateMergeElement(List oldElements, List designItemDetails) { @@ -1010,9 +1280,32 @@ public class DesignServiceImpl extends ServiceImpl impleme //group 下面没有元素时候 直接删除 // userLikeGroupService.removeById(userLike.getUserLikeGroupId()); } + + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(UserLikeSort::getUserLikeGroupId, userLike.getUserLikeGroupId()); + qw.lambda().orderByDesc(UserLikeSort::getSort); + List userLikeSorts = userLikeSortMapper.selectList(qw); + UserLikeSort userLikeSort = getUserLikeSortByUserLikeId(userLike.getId()); + Long userLikeSortId = userLikeSort.getId(); + for (UserLikeSort likeSort : userLikeSorts) { + if (Objects.equals(likeSort.getId(), userLikeSortId)) { + userLikeSortMapper.deleteById(likeSort); + break; + }else { + likeSort.setSort(likeSort.getSort() - 1); + userLikeSortMapper.updateById(likeSort); + } + } return Boolean.TRUE; } + private UserLikeSort getUserLikeSortByUserLikeId(Long userLikeId) { + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(UserLikeSort::getUserLikeId, userLikeId); + UserLikeSort userLikeSort = userLikeSortMapper.selectOne(qw); + return userLikeSort; + } + @Override public String generateHighDesign(GenerateHighDesignDTO generateHighDesignDTO) { DesignItem designItem = designItemService.getById(generateHighDesignDTO.getDesignItemId()); @@ -1389,4 +1682,334 @@ public class DesignServiceImpl extends ServiceImpl impleme } } -} + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean receiveDesignResults(JSONObject responseJSONObject) { +// String requestId = "UUID.randomUUID().toString()"; + String requestId = responseJSONObject.getString("requestId"); + Map context; + synchronized (designContext) { + context = designContext.get(requestId); + if (context == null) { + log.error("上下文数据缺失,无法完成操作"); + return false; + } + + DesignPythonObjects pythonObjects = (DesignPythonObjects) context.get("pythonObjects"); + Long designId = (Long) context.get("designId"); + Long collectionId = (Long) context.get("collectionId"); + AuthPrincipalVo userInfo = (AuthPrincipalVo) context.get("userInfo"); + String timeZone = (String) context.get("timeZone"); + String singleOverall = (String) context.get("singleOverall"); + + DesignCollectionVO designCollectionVO = savePythonDesignItemAndDetailSingle(pythonObjects, designId, collectionId, userInfo, timeZone, responseJSONObject, singleOverall, context); + +// log.info(designContext.toString()); + designContext.put(requestId, context); + } + + return Boolean.TRUE; + } + + @Override + public DesignCollectionVO getDesignResult(String requestId, List objectSignList) { +// Map stringObjectMap = designContext.get("UUID.randomUUID().toString()"); + Map stringObjectMap = designContext.get(requestId); +// log.info(stringObjectMap.toString()); + DesignCollectionVO result = (DesignCollectionVO) stringObjectMap.get("DesignCollectionVO"); + if (Objects.isNull(result)) { + DesignCollectionVO noneResult = new DesignCollectionVO(); + noneResult.setUnfinishedList(objectSignList); + return noneResult; + } + for (DesignCollectionItemVO designCollectionItem : result.getDesignCollectionItems()) { + String objectSign = designCollectionItem.getObjectSign(); + objectSignList.remove(objectSign); + } + result.setUnfinishedList(objectSignList); + return result; + } + + @Override + public String designCloud(DesignCollectionDTO designDTO) { + AuthPrincipalVo userInfo = UserContext.getUserHolder(); + //校验collection element + ValidateElementVO elementVO = collectionElementService.validateElement(designDTO); + //design + return designBatch(designDTO, userInfo, null, elementVO); + } + + private String designBatch(DesignCollectionDTO designDTO, AuthPrincipalVo userInfo, Long collectionIdParam, ValidateElementVO elementVO) { + if (CollectionUtil.isNotEmpty(designDTO.getSketchBoards())) { + //编辑sketchBoard + collectionElementService.editSketchBoardsElement(elementVO, designDTO.getSketchBoards()); + } + if (CollectionUtil.isNotEmpty(designDTO.getPrintBoards())) { + //编辑printBoard + collectionElementService.editPrintBoardsElement(elementVO, designDTO.getPrintBoards()); + } + //保存collection + Long collectionId; + if (null == collectionIdParam) { + collectionId = collectionService.saveCollection(userInfo.getId(), designDTO.getTimeZone(), designDTO.getMoodTemplateId(), designDTO.getMoodboardPosition()); + }else { + collectionId = collectionIdParam; + } + List elementIds = getElementId(elementVO); + //批量关联element 到 collection + collectionElementService.relationCollection(elementIds, collectionId); + //library转化为collection(生成) + saveCollectionElemntsByLibrarys(elementVO, collectionId); + //generate转化为collection(生成) + saveCollectionElemntsByGenerates(elementVO, collectionId); + //保存颜色版 + collectionElementService.saveColorBoard(designDTO.getColorBoards(), collectionId, designDTO.getTimeZone()); + //保存design + Long designId = saveOne(designDTO, collectionId, userInfo.getId()); + //计算library +// calculateLibraryAndSysFile(designDTO, elementVO, userInfo); + //组装design入参 + long startTime = System.currentTimeMillis(); + DesignPythonObjects pythonObjects = pythonService.covertDesignParam(designDTO.getSystemScale(), + designDTO.getSingleOverall(), designDTO.getSwitchCategory(), elementVO, designDTO.getProcessId()); + long endTime = System.currentTimeMillis(); + long totalTimeInSeconds = (endTime - startTime) / 1000; + log.info("组装入参运行时间:" + totalTimeInSeconds + " 秒"); + // pythonObjects增加image_id关联 + startTime = System.currentTimeMillis(); + List imageIds = relationImageIds(pythonObjects); + endTime = System.currentTimeMillis(); + totalTimeInSeconds = (endTime - startTime) / 1000; + log.info("增加image_id关联运行时间:" + totalTimeInSeconds + " 秒"); + //design + startTime = System.currentTimeMillis(); + String requestId = UUID.randomUUID().toString(); + pythonObjects.setRequestId(requestId); + AuthPrincipalVo userHolder = UserContext.getUserHolder(); + String taskId = pythonService.designBatch(pythonObjects, userHolder.getId(), elementVO.getDesignNum(), requestId); + + DesignBatch designBatch = new DesignBatch(); + + designBatch.setAccountId(userInfo.getId()); + designBatch.setDesignId(designId); + designBatch.setCollectionId(collectionId); + designBatch.setTaskId(taskId); + designBatch.setCreateTime(LocalDateTime.now()); + designBatch.setStatus(0); + designBatch.setTotalNum(elementVO.getDesignNum()); + designBatchMapper.insert(designBatch); + + endTime = System.currentTimeMillis(); + totalTimeInSeconds = (endTime - startTime) / 1000; + log.info("design python端运行时间:" + totalTimeInSeconds + " 秒"); + //生成library + startTime = System.currentTimeMillis(); + generateLibrary(elementVO, designDTO.getTimeZone()); + //处理关联关系,修复element覆盖得情况 + List relationElements = collectionElementService.getByOnlyCollectionId(collectionId); + List relationElementIds = relationElements.stream().map(CollectionElement::getId).collect(Collectors.toList()); + handleCollectionElementRelation(collectionId, null != collectionIdParam, relationElementIds); + endTime = System.currentTimeMillis(); + totalTimeInSeconds = (endTime - startTime) / 1000; + log.info("处理关联关系运行时间:" + totalTimeInSeconds + " 秒"); + + + Map context = new HashMap<>(); + context.put("pythonObjects", pythonObjects); // 转换后的 Python 请求参数 + context.put("designId", designId); // 设计 ID + context.put("collectionId", collectionId); // 集合 ID + context.put("userInfo", userInfo); // 用户信息 + context.put("timeZone", designDTO.getTimeZone()); // 时区 + context.put("singleOverall", designDTO.getSingleOverall()); // 其他设计参数 + context.put("requestIdList", elementVO.getRequestIdList()); + + // 将上下文存入全局设计上下文中 + designContext.put(taskId, context); + return taskId; + } + + @Override + public void processDesignBatch(Map designBatchResult) { + Object progress = designBatchResult.get("progress"); + if (progress instanceof String) { + if (progress.equals("0/100")) { + return; + } + if (progress.equals("ok")) { + String taskId = (String) designBatchResult.get("task_id"); + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(DesignBatch::getTaskId, taskId); + List designBatches = designBatchMapper.selectList(qw); + if (CollectionUtil.isNotEmpty(designBatches)) { + DesignBatch designBatch = designBatches.get(0); + designBatch.setStatus(1); + designBatchMapper.updateById(designBatch); + } + } + }else { + String taskId = (String) designBatchResult.get("task_id"); + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(DesignBatch::getTaskId, taskId); + List designBatches = designBatchMapper.selectList(qw); + if (CollectionUtil.isNotEmpty(designBatches)) { + DesignBatch designBatch = designBatches.get(0); + if (designBatch.getCompletedNum() == null) { + designBatch.setCompletedNum(1); + }else { + designBatch.setCompletedNum(designBatch.getCompletedNum() + 1); + } + designBatchMapper.updateById(designBatch); + } + + Integer i = (Integer) progress; + Map context; + synchronized (designContext) { + context = designContext.get(taskId); + if (context == null) { + log.error("上下文数据缺失,无法完成操作"); + return; + } + + DesignPythonObjects pythonObjects = (DesignPythonObjects) context.get("pythonObjects"); + Long designId = (Long) context.get("designId"); + Long collectionId = (Long) context.get("collectionId"); + AuthPrincipalVo userInfo = (AuthPrincipalVo) context.get("userInfo"); + String timeZone = (String) context.get("timeZone"); + String singleOverall = (String) context.get("singleOverall"); + + DesignPythonObject item = pythonObjects.getObjects().get(i); + DesignItem designItem = new DesignItem(); + designItem.setAccountId(userInfo.getId()); + designItem.setCollectionId(collectionId); + designItem.setDesignId(designId); + designItem.setCreateDate(DateUtil.getByTimeZone(timeZone)); + //生成的八张图片 + designItem.setDesignUrl(item.getBasic().getSave_name()); + designItem.setHasLike((byte) 0); + //生成designItem + Long designItemId = designItemService.saveOne(designItem); + // python design返回入库及封装 + JSONObject outfit = (JSONObject) designBatchResult.get("result"); + if (null == outfit) { + return; + } + TDesignPythonOutfit designPythonOutfit = new TDesignPythonOutfit(); + designPythonOutfit.setDesignItemId(designItemId); + designPythonOutfit.setUserId(userInfo.getId()); + designPythonOutfit.setDesignId(designId); + designPythonOutfit.setCollectionId(collectionId); + String synthesisUrl = outfit.getString("synthesis_url"); + if (!StringUtils.isEmpty(synthesisUrl)) { + designPythonOutfit.setDesignUrl(synthesisUrl); + } else { + throw new BusinessException("design.interface.exception"); + } + designPythonOutfitService.save(designPythonOutfit); + + JSONArray layers = outfit.getJSONArray("layers"); + List list = new ArrayList<>(); + DesignCollectionItemVO designCollectionItemVO = new DesignCollectionItemVO(); + for (int i1 = 0; i1 < layers.size(); i1++) { + JSONObject jsonObject = layers.getJSONObject(i1); + TDesignPythonOutfitDetail designPythonOutfitDetail = new TDesignPythonOutfitDetail(); + designPythonOutfitDetail.setDesignId(designId); + designPythonOutfitDetail.setDesignPythonOutfitId(designPythonOutfit.getId()); + designPythonOutfitDetail.setPosition(jsonObject.getString("position")); + designPythonOutfitDetail.setImageSize(jsonObject.getString("image_size")); + designPythonOutfitDetail.setImageUrl(jsonObject.getString("image_url")); + if (singleOverall.equals(SingleOverallEnum.SINGLE.getRealName())) { + designCollectionItemVO.setDesignItemUrl(designItem.getDesignUrl()); + } + designPythonOutfitDetail.setImageCategory(jsonObject.getString("image_category")); + designPythonOutfitDetail.setMaskUrl(jsonObject.getString("mask_url")); + designPythonOutfitDetail.setUserId(userInfo.getId()); + designPythonOutfitDetail.setPriority(Integer.parseInt(jsonObject.getString("priority"))); + designPythonOutfitDetail.setCreateDate(LocalDateTime.now()); + list.add(designPythonOutfitDetail); + } + designPythonOutfitDetailService.saveBatch(list); + designCollectionItemVO.setDesignItemId(designItemId); + designCollectionItemVO.setDesignItemUrl(designItem.getDesignUrl()); + designCollectionItemVO.setDesignOutfitId(designPythonOutfit.getId()); + String designUrl = designPythonOutfit.getDesignUrl(); + if (!StringUtils.isEmpty(designUrl) && designUrl.contains("/")) { + int firstIndex = designUrl.indexOf("/"); + designCollectionItemVO.setDesignOutfitUrl(minioUtil.getPreSignedUrl(designUrl.substring(0, firstIndex) + "/" + designUrl.substring(firstIndex + 1), 24 * 60)); + } + //response +// designCollectionItems.add(designCollectionItemVO); + + List designItemDetails = Lists.newArrayList(); + Map typePriority = list.stream().collect(Collectors.toMap(d -> d.getImageCategory().split("_")[0], + d -> Math.abs(d.getPriority()), + (existing, replacement) -> replacement)); + Map typeAndUndividedLayer = designItemService.setTypeAndUndividedLayer(layers); + for (DesignPythonItem detail : item.getItems()) { + if (null == detail) { + continue; + } + DesignItemDetail designItemDetail = CopyUtil.copyObject(detail, DesignItemDetail.class); + designItemDetail.setAccountId(userInfo.getId()); + designItemDetail.setDesignId(designId); + designItemDetail.setDesignItemId(designItemId); + designItemDetail.setCollectionElementId(detail.getElementId()); + designItemDetail.setCreateDate(DateUtil.getByTimeZone(timeZone)); + designItemDetail.setUndividedLayer(typeAndUndividedLayer.get(designItemDetail.getType().toLowerCase())); + if (SysFileLevel2TypeEnum.BODY.getRealName().equals(detail.getType())) { + designItemDetail.setPath(detail.getBody_path()); + //BODY不关联businessId + designItemDetail.setBusinessId(0L); + } + designItemDetail.setIconPath(detail.getIcon()); + designItemDetail.setPriority(typePriority.get(detail.getType().toLowerCase())); + if (!detail.getType().equals("Body")){ + DesignPythonItemPrint printObject = detail.getPrint().getOverall(); +// designItemDetail.setPrintPath(Objects.isNull(printObject) ? "" : printObject.getPath()); + designItemDetail.setPrintPath(CollectionUtils.isEmpty(printObject.getPrint_path_list()) ? "" : printObject.getPrint_path_list().get(0)); + } + designItemDetailService.save(designItemDetail); + if (!SysFileLevel2TypeEnum.BODY.getRealName().equals(detail.getType()) && !StringUtil.isNullOrEmpty(designItemDetail.getPrintPath())) { + DesignItemDetailPrint print = new DesignItemDetailPrint(); + print.setDesignItemDetailId(designItemDetail.getId()); + print.setPrintType("print"); + print.setPath(designItemDetail.getPrintPath()); + print.setSingleOrOverall("overall"); + print.setPosition("[0.0,0.0]"); +// print.setScale(1d); + // todo mark 将print默认scale置为0.3 + print.setScale(0.3d); + print.setAngle(0.0); + print.setPriority(1); + QueryWrapper getPrintboardLevel2TypeQw = new QueryWrapper<>(); + getPrintboardLevel2TypeQw.lambda().eq(CollectionElement::getUrl, print.getPath()); + getPrintboardLevel2TypeQw.lambda().orderByDesc(CollectionElement::getCreateDate); + getPrintboardLevel2TypeQw.last("limit 1"); + CollectionElement one = collectionElementService.getOne(getPrintboardLevel2TypeQw); + print.setLevel2Type(one.getLevel2Type()); + print.setCreateDate(LocalDateTime.now()); + designItemDetailPrintService.save(print); + } + } + } + +// for (int i = 0; i < pythonObjects.getObjects().size(); i++) { +// +// } +// response.setProcessId(pythonObjects.getProcess_id()); + return; + } + + } + + @Override + public Boolean sort(UserLikeSortDTO userLikeSortDTO) { +// QueryWrapper qw = new QueryWrapper<>(); +// qw.lambda().eq(UserLikeSort::getUserLikeGroupId, userLikeSortDTO.getUserLikeGroupId()); +// userLikeSortMapper.delete(qw); + for (UserLikeSort userLikeSort : userLikeSortDTO.getUserLikeSortList()) { + userLikeSortMapper.updateById(userLikeSort); + } + return Boolean.TRUE; + } + +} \ No newline at end of file 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 ec5d5774..4a7b5cc0 100644 --- a/src/main/java/com/ai/da/service/impl/GenerateServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/GenerateServiceImpl.java @@ -289,7 +289,7 @@ public class GenerateServiceImpl extends ServiceImpl i if (b) creditsService.insertToCreditsDetail(accountId, CreditsEventsEnum.TO_PRODUCT_IMAGE.getName(), CreditsEventsEnum.TO_PRODUCT_IMAGE.getValue(), - "negative"); + "negative", null); } } @@ -524,7 +524,7 @@ public class GenerateServiceImpl extends ServiceImpl i times = 1; } } - // Slogan 参数校验 + // Slogan 参数校验 slogan目前只能开一个接口。所以只有生产环境上能使用 if (generateThroughImageTextDTO.getLevel2Type().equals(CollectionLevel2TypeEnum.SLOGAN.getRealName())) { if (StringUtil.isNullOrEmpty(generateThroughImageTextDTO.getSloganBase64())) { log.error("Printboard-Slogan模式下,slogan image为空"); @@ -592,7 +592,7 @@ public class GenerateServiceImpl extends ServiceImpl i // 2、判断用户当前积分是否够本次生成消耗 Boolean preDeduction = creditsService.creditsPreDeduction(creditsEventsEnum, 1); if (!preDeduction) { - throw new BusinessException("remaining.credits.insufficient"); + throw new BusinessException("remaining.credits.insufficient", ResultEnum.WARNING.getCode()); } // 3、生成唯一id 使用uuid,由于uuid重复的几率很小,故取消对uuid重复性的校验 @@ -772,7 +772,7 @@ public class GenerateServiceImpl extends ServiceImpl i if (b) creditsService.insertToCreditsDetail(accountId, CreditsEventsEnum.RELIGHT.getName(), CreditsEventsEnum.RELIGHT.getValue(), - "negative"); + "negative", null); } } 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 416d84d2..6c63b002 100644 --- a/src/main/java/com/ai/da/service/impl/OrderInfoServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/OrderInfoServiceImpl.java @@ -4,15 +4,20 @@ 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.enums.ProductEnum; import com.ai.da.common.response.PageBaseResponse; import com.ai.da.common.utils.OrderNoUtils; +import com.ai.da.common.utils.RequestInfoUtil; import com.ai.da.mapper.primary.OrderInfoMapper; +import com.ai.da.mapper.primary.PaymentInfoMapper; import com.ai.da.mapper.primary.ProductMapper; import com.ai.da.mapper.primary.entity.OrderInfo; +import com.ai.da.mapper.primary.entity.PaymentInfo; 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.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; @@ -21,11 +26,14 @@ import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; +import java.util.Map; +import java.util.Objects; @Service @Slf4j @@ -34,8 +42,12 @@ public class OrderInfoServiceImpl extends ServiceImpl queryWrapper = new QueryWrapper<>(); queryWrapper.eq("order_no", orderNo); OrderInfo orderInfo = baseMapper.selectOne(queryWrapper); - if(orderInfo == null){ + if (orderInfo == null) { return null; } return orderInfo.getOrderStatus(); @@ -129,6 +185,7 @@ public class OrderInfoServiceImpl extends ServiceImpl getOrderByPage(QueryPageByTimeDTO queryPageByTimeDTO){ + public PageBaseResponse getOrderByPage(QueryPageByTimeDTO queryPageByTimeDTO) { QueryWrapper qw = new QueryWrapper<>(); - qw.eq("account_id",UserContext.getUserHolder().getId()); + qw.eq("account_id", UserContext.getUserHolder().getId()); String startTime = queryPageByTimeDTO.getStartTime(); String endTime = queryPageByTimeDTO.getEndTime(); - if (StringUtil.isNullOrEmpty(startTime)){ + if (StringUtil.isNullOrEmpty(startTime)) { startTime = "2024-02-01 00:00:00"; } - if (StringUtil.isNullOrEmpty(endTime)){ + if (StringUtil.isNullOrEmpty(endTime)) { LocalDateTime now = LocalDateTime.now(); DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); endTime = now.format(dateTimeFormatter); @@ -206,11 +265,28 @@ public class OrderInfoServiceImpl extends ServiceImpl qw = new QueryWrapper<>(); + qw.eq("order_no", orderNo); + List paymentInfos = paymentInfoMapper.selectList(qw); + Float sum = paymentInfos.stream() + .map(PaymentInfo::getPayerTotal) + .reduce(0f, Float::sum); + + baseMapper.update( + new OrderInfo(), + new UpdateWrapper() + .eq("order_no", orderNo) + .set("total_fee", sum) + ); + } + } diff --git a/src/main/java/com/ai/da/service/impl/PayPalCheckoutServiceImpl.java b/src/main/java/com/ai/da/service/impl/PayPalCheckoutServiceImpl.java index 22c57621..98595177 100644 --- a/src/main/java/com/ai/da/service/impl/PayPalCheckoutServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/PayPalCheckoutServiceImpl.java @@ -9,6 +9,7 @@ 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.model.dto.ProductPurchaseDTO; import com.ai.da.service.*; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; @@ -84,17 +85,17 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService { */ @Override @Transactional(rollbackFor = Exception.class) - public HashMap createOrder(Integer amount, String returnUrl) throws SerializeException { + public HashMap createOrder(ProductPurchaseDTO productPurchaseDTO, HttpServletRequest request) throws SerializeException { // 生成订单 log.info("生成订单"); - OrderInfo orderInfo = orderInfoService.createOrderByProductId(amount, PayTypeEnum.PAYPAL.getType()); + OrderInfo orderInfo = orderInfoService.createOrderByProductId(productPurchaseDTO.getQuantity(), PayTypeEnum.PAYPAL.getType(), request); - OrdersCreateRequest request = new OrdersCreateRequest(); - request.header("prefer", "return=representation"); - request.requestBody(buildRequestBody(String.valueOf(orderInfo.getTotalFee()), returnUrl)); + OrdersCreateRequest paypalRequest = new OrdersCreateRequest(); + paypalRequest.header("prefer", "return=representation"); + paypalRequest.requestBody(buildRequestBody(String.valueOf(orderInfo.getTotalFee()), productPurchaseDTO.getReturnUrl())); HttpResponse response = null; try { - response = payPalClient.client(mode, clientId, clientSecret).execute(request); + response = payPalClient.client(mode, clientId, clientSecret).execute(paypalRequest); } catch (Exception e) { log.error("调用paypal订单创建失败,失败原因 ===> {}", e.getMessage()); throw new BusinessException("Order creation failed"); @@ -441,11 +442,11 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService { * 申请退款 */ @Transactional(rollbackFor = Exception.class) - public Boolean refundOrder(String orderId, String reason) throws IOException { + public Boolean refundOrder(String orderNo, String reason) throws IOException { - RefundInfo refundByOrderNo = refundsInfoService.createRefundByOrderNo(orderId, reason); + RefundInfo refundByOrderNo = refundsInfoService.createRefundByOrderNo(orderNo, reason); - OrdersGetRequest ordersGetRequest = new OrdersGetRequest(orderId); + OrdersGetRequest ordersGetRequest = new OrdersGetRequest(orderNo); PayPalClient payPalClient = new PayPalClient(); HttpResponse ordersGetResponse = null; ordersGetRequest.authorization("Bearer " + getOAuth()); @@ -461,7 +462,7 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService { request.authorization("Bearer " + getOAuth()); request.prefer("return=representation"); - OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderId); + OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderNo); request.requestBody(buildRefundRequestBody(String.valueOf(orderInfo.getTotalFee()), reason)); HttpResponse response = null; try { @@ -476,7 +477,7 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService { //进行数据库操作,修改状态为已退款(配合回调和退款查询确定退款成功) //更新订单状态 - orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.REFUND_SUCCESS); + orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.REFUND_SUCCESS); refundsInfoService.updateRefundForPayPal( refundByOrderNo.getId(), @@ -485,14 +486,14 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService { AliPayTradeStateEnum.REFUND_SUCCESS.getType()); //退款成功 // 更新积分状态 - OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderId); + OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderNo); // creditsService.creditsRefund(orderByOrderNo.getAccountId(), orderByOrderNo.getTotalFee() / Integer.parseInt(CreditsEventsEnum.PRICE.getValue())); - creditsService.creditsRefund(orderByOrderNo.getAccountId(), (int)(orderByOrderNo.getTotalFee() / Float.parseFloat(CreditsEventsEnum.PRICE.getValue()))); + creditsService.creditsRefund(orderByOrderNo.getAccountId(), (int)(orderByOrderNo.getTotalFee() / Float.parseFloat(CreditsEventsEnum.PRICE.getValue())), orderNo); log.info("退款成功"); result = Boolean.TRUE; } else { //更新订单状态 - orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.REFUND_ABNORMAL); + orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.REFUND_ABNORMAL); //更新退款单 refundsInfoService.updateRefundForPayPal( @@ -571,21 +572,21 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService { // 处理当前订单 @Transactional(rollbackFor = Exception.class) - public void processOrder(String orderId) { + public void processOrder(String orderNo) { // 1、确定当前订单是否已经被扣款 - OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderId); + OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderNo); if (orderInfo.getOrderStatus().equals(OrderStatusEnum.SUCCESS.getType())) { // 直接返回 return; } // 发起扣款请求 - Order capturedOrder = captureOrder(orderId); + Order capturedOrder = captureOrder(orderNo); // 业务处理 if (PayPalOrderStatusEnum.COMPLETED.getStatus().equals(capturedOrder.status())) { //更新订单状态 - orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.SUCCESS); + orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.SUCCESS); //记录支付日志 - paymentInfoService.createPaymentInfoForPayPal(capturedOrder); + paymentInfoService.createPaymentInfoForPayPal(capturedOrder, "credits"); float quantity = orderInfo.getTotalFee() / Float.parseFloat(CreditsEventsEnum.PRICE.getValue()); // 更新积分 creditsService.buyCredits(orderInfo.getAccountId(), quantity); @@ -593,7 +594,7 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService { creditsService.insertToCreditsDetail(orderInfo.getAccountId(), CreditsEventsEnum.BUY_CREDITS.getName() + "--PayPal", String.valueOf((Long.parseLong(CreditsEventsEnum.BUY_CREDITS.getValue()) * quantity)), - "positive"); + "positive", orderNo); } } @@ -628,7 +629,7 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService { //如果订单已支付,则更新商户端订单状态 orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.SUCCESS); //并记录支付日志 - paymentInfoService.createPaymentInfoForPayPal(result); + paymentInfoService.createPaymentInfoForPayPal(result, "credits"); float quantity = orderByOrderNo.getTotalFee() / Float.parseFloat(CreditsEventsEnum.PRICE.getValue()); // 更新积分 // creditsService.buyCredits(orderByOrderNo.getAccountId(),orderByOrderNo.getTotalFee() / Integer.parseInt(CreditsEventsEnum.PRICE.getValue())); @@ -637,7 +638,7 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService { creditsService.insertToCreditsDetail(orderByOrderNo.getAccountId(), CreditsEventsEnum.BUY_CREDITS.getName() + "--Paypal", String.valueOf((Long.parseLong(CreditsEventsEnum.BUY_CREDITS.getValue()) * quantity)), - "positive"); + "positive", orderNo); } } 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 2a80a4f5..f5db3e24 100644 --- a/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java @@ -1,27 +1,53 @@ package com.ai.da.service.impl; +import com.ai.da.common.context.UserContext; import com.ai.da.common.enums.PayTypeEnum; +import com.ai.da.common.response.PageBaseResponse; import com.ai.da.mapper.primary.PaymentInfoMapper; +import com.ai.da.mapper.primary.entity.OrderInfo; import com.ai.da.mapper.primary.entity.PaymentInfo; import com.ai.da.model.dto.AlipayHKCallbackDTO; -import com.ai.da.service.PaymentInfoService; +import com.ai.da.model.dto.QueryPageByTimeDTO; +import com.ai.da.model.vo.OrderListVO; +import com.ai.da.service.*; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.google.gson.Gson; import com.paypal.orders.Order; +import com.stripe.Stripe; +import com.stripe.exception.StripeException; +import com.stripe.model.Charge; +import com.stripe.model.Invoice; +import com.stripe.model.Subscription; import com.stripe.model.checkout.Session; +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.util.CollectionUtils; +import javax.annotation.Resource; import java.math.BigDecimal; import java.math.RoundingMode; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Objects; @Service @Slf4j public class PaymentInfoServiceImpl extends ServiceImpl implements PaymentInfoService { + @Resource + private StripeService stripeService; + + @Resource + private OrderInfoService orderInfoService; + /** * 记录支付日志:微信支付 * @param plainText @@ -50,7 +76,6 @@ public class PaymentInfoServiceImpl extends ServiceImpl params) { + public void createPaymentInfoForAliPay(Map params, String type) { log.info("记录支付日志"); @@ -83,20 +108,26 @@ public class PaymentInfoServiceImpl extends ServiceImpl qw = new QueryWrapper<>(); - qw.eq("order_no", orderId); + @Value("${stripe.private-key}") + private String privateKey; + public PaymentInfo createOrUpdatePaymentInfoForStripe(Invoice invoice){ + Stripe.apiKey = privateKey; + // 获取transactionId,从sessionId更改为invoiceId + String invoiceId = invoice.getId(); - return baseMapper.selectOne(qw); + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("transaction_id", invoiceId); + PaymentInfo paymentInfo = baseMapper.selectOne(qw); + String status = invoice.getStatus(); + // 判断当前支付是否已经被记录,确保同一个支付不会被重复记录 + if (Objects.isNull(paymentInfo)){ + String orderNo; + try { + if (invoice.getBillingReason().equals("manual")){ + // 手动创建的发票,针对one-time支付 + orderNo = invoice.getLines().getData().get(0).getPrice().getMetadata().get("orderId"); + }else { + String subscriptionId = invoice.getSubscription(); + // 从subscription中获取orderNo + orderNo = Subscription.retrieve(subscriptionId).getDescription().replace("AiDA - ", ""); + } + } catch (StripeException e) { + throw new RuntimeException(e); + } + Long amountTotal; + if (status.equals("paid")){ + amountTotal = invoice.getAmountPaid(); + }else { + amountTotal = invoice.getAmountDue(); + } + + // stripe 的支付金额单位是分,在我们数据库中金额单位为 元 + Float divide = new BigDecimal(amountTotal).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP).floatValue(); + String type = invoice.getBillingReason().equals("subscription_create") ? "new" : + invoice.getBillingReason().equals("subscription_cycle") ? "renewal" : invoice.getBillingReason(); + + // 获取支付方式 + Map paymentMethod = stripeService.getPaymentMethodByInvoiceId(invoiceId); + // 获取订单信息 + OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderNo); + + paymentInfo = new PaymentInfo(); + paymentInfo.setOrderNo(orderNo); + paymentInfo.setPaymentType(PayTypeEnum.STRIPE.getType()); + paymentInfo.setTransactionId(invoiceId); + // 使用invoice的状态 + paymentInfo.setTradeState(status); + paymentInfo.setPayerTotal(divide); + Gson gson = new Gson(); + String json = gson.toJson(invoice); + paymentInfo.setContent(json); + paymentInfo.setType(type); + paymentInfo.setNotified(0); + paymentInfo.setPaymentMethod(paymentMethod.get("paymentMethod")); + paymentInfo.setLast4(paymentMethod.get("last4")); + paymentInfo.setHostedInvoiceUrl(invoice.getHostedInvoiceUrl()); + paymentInfo.setCreateTime(LocalDateTime.now()); + if (!Objects.isNull(orderByOrderNo)){ + paymentInfo.setCountry(orderByOrderNo.getCountry()); + paymentInfo.setCity(orderByOrderNo.getCity()); + paymentInfo.setIpAddress(orderByOrderNo.getIpAddress()); + } + baseMapper.insert(paymentInfo); + }else { + paymentInfo.setTradeState(status); + paymentInfo.setUpdateTime(LocalDateTime.now()); + baseMapper.updateById(paymentInfo); + } + return paymentInfo; + } + + public PaymentInfo createOrUpdatePaymentInfoForStripe(Charge charge){ + Stripe.apiKey = privateKey; + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("transaction_id", charge.getInvoice()); + PaymentInfo paymentInfo = baseMapper.selectOne(qw); + Charge.PaymentMethodDetails paymentMethodDetails = charge.getPaymentMethodDetails(); + String paymentMethod; + String last4 = "N/A"; + switch (paymentMethodDetails.getType()){ + case "alipay": + paymentMethod = "Alipay"; + break; + case "bancontact": + paymentMethod = "BanContact"; + break; + case "card": + Charge.PaymentMethodDetails.Card card = paymentMethodDetails.getCard(); + String brand = card.getBrand(); + brand = brand.substring(0, 1).toUpperCase() + brand.substring(1); + paymentMethod = brand + " " + card.getFunding() + "card"; + last4 = card.getLast4(); + break; + case "eps": + Charge.PaymentMethodDetails.Eps eps = paymentMethodDetails.getEps(); + paymentMethod = eps.getBank(); + break; + case "giropay": + paymentMethod = "GiroPay"; + break; + case "ideal": + Charge.PaymentMethodDetails.Ideal ideal = paymentMethodDetails.getIdeal(); + paymentMethod = ideal.getBank(); + break; + case "link": + paymentMethod = "Link"; + break; + default: + paymentMethod = "N/A"; + } + if (Objects.isNull(paymentInfo)){ + Stripe.apiKey = privateKey; + + String orderNo = charge.getDescription().replace("AiDA - ", ""); + OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderNo); + Float divide = new BigDecimal(charge.getAmount()).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP).floatValue(); + paymentInfo = new PaymentInfo(); + paymentInfo.setOrderNo(orderNo); + paymentInfo.setTransactionId(charge.getInvoice()); + paymentInfo.setPaymentType(PayTypeEnum.STRIPE.getType()); + paymentInfo.setTradeState(charge.getStatus()); + paymentInfo.setPayerTotal(divide); + paymentInfo.setNotified(0); + paymentInfo.setPaymentMethod(paymentMethod); + paymentInfo.setLast4(last4); + paymentInfo.setCreateTime(LocalDateTime.now()); + if (!Objects.isNull(orderByOrderNo)){ + paymentInfo.setCountry(orderByOrderNo.getCountry()); + paymentInfo.setCity(orderByOrderNo.getCity()); + paymentInfo.setIpAddress(orderByOrderNo.getIpAddress()); + } + baseMapper.insert(paymentInfo); + }else { + paymentInfo.setTradeState(charge.getStatus()); + paymentInfo.setPaymentMethod(paymentMethod); + paymentInfo.setLast4(last4); + paymentInfo.setUpdateTime(LocalDateTime.now()); + baseMapper.updateById(paymentInfo); + } + + return paymentInfo; + } + + + @Override + public List getPaymentInfoByOrderNo(String orderId, String order){ + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("order_no", orderId).orderByDesc(order.equals("DESC"),"id"); + + return baseMapper.selectList(qw); } @Override @@ -190,4 +384,37 @@ public class PaymentInfoServiceImpl extends ServiceImpl getPaymentInfo(QueryPageByTimeDTO queryPageByTimeDTO){ + Long accountId = 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); + } + + int offset = (queryPageByTimeDTO.getPage() - 1) * queryPageByTimeDTO.getSize(); + List orderListVOS = baseMapper.selectPageOrderList(accountId, startTime, endTime, offset, + queryPageByTimeDTO.getSize(), queryPageByTimeDTO.getId()); + + if (CollectionUtils.isEmpty(orderListVOS)) { + return PageBaseResponse.success(new Page<>()); + }else { + int totalCount = baseMapper.queryOrderListTotalCount(accountId, startTime, endTime, queryPageByTimeDTO.getId()); + IPage orderListVOIPage = new Page<>(); + Integer size = queryPageByTimeDTO.getSize(); + orderListVOIPage.setSize(size); + orderListVOIPage.setRecords(orderListVOS); + orderListVOIPage.setCurrent(queryPageByTimeDTO.getPage()); + orderListVOIPage.setPages((long)Math.ceil((double) totalCount / size)); + orderListVOIPage.setTotal(totalCount); + return PageBaseResponse.success(orderListVOIPage); + } + } } diff --git a/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java b/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java index 7951adcd..f4699890 100644 --- a/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java @@ -1,16 +1,23 @@ package com.ai.da.service.impl; +import com.ai.da.common.config.exception.BusinessException; +import com.ai.da.common.constant.CommonConstant; import com.ai.da.common.context.UserContext; -import com.ai.da.common.enums.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.common.utils.OrderNoUtils; +import com.ai.da.common.enums.*; +import com.ai.da.common.utils.DateUtil; +import com.ai.da.common.utils.SendEmailUtil; import com.ai.da.mapper.primary.AccountMapper; +import com.ai.da.mapper.primary.PaymentInfoMapper; +import com.ai.da.mapper.primary.SubscriptionInfoMapper; import com.ai.da.mapper.primary.entity.OrderInfo; import com.ai.da.mapper.primary.entity.PaymentInfo; import com.ai.da.mapper.primary.entity.RefundInfo; +import com.ai.da.mapper.primary.entity.SubscriptionInfo; +import com.ai.da.model.dto.ProductPurchaseDTO; +import com.ai.da.model.dto.SubscriptionEmailParamsDTO; import com.ai.da.service.*; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.google.gson.Gson; import com.stripe.Stripe; @@ -21,6 +28,7 @@ import com.stripe.model.checkout.Session; import com.stripe.net.Webhook; import com.stripe.param.*; import com.stripe.param.checkout.SessionCreateParams; +import io.netty.util.internal.StringUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -30,10 +38,12 @@ import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.math.BigDecimal; import java.math.RoundingMode; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.*; +@SuppressWarnings("LoggingSimilarMessage") @Service @Slf4j public class StripeServiceImpl implements StripeService { @@ -48,9 +58,15 @@ public class StripeServiceImpl implements StripeService { private CreditsService creditsService; @Resource private RefundInfoService refundInfoService; + @Resource + private AccountService accountService; @Resource private AccountMapper accountMapper; + @Resource + private SubscriptionInfoMapper subscriptionInfoMapper; + @Resource + private PaymentInfoMapper paymentInfoMapper; @Value("${stripe.private-key}") private String privateKey; @@ -58,55 +74,87 @@ public class StripeServiceImpl implements StripeService { @Value("${stripe.webhook-sign-secret}") private String signSecret; + @Value("${orderList.link}") + private String orderListLink; + @Override @Transactional(rollbackFor = Exception.class) - public String pay(Integer quantity, String returnUrl) { + public String pay(ProductPurchaseDTO productPurchaseDTO, HttpServletRequest request) { Stripe.apiKey = privateKey; - log.info("生成订单"); - OrderInfo orderInfo = orderInfoService.createOrderByProductId(quantity, PayTypeEnum.STRIPE.getType()); - Long id = UserContext.getUserHolder().getId(); - com.ai.da.mapper.primary.entity.Account account = accountMapper.selectById(id); + ProductEnum productEnum; + switch (productPurchaseDTO.getProductName()){ + case "CreditsPurchase": + productEnum = ProductEnum.CreditsProduct; + productPurchaseDTO.setAutoRenewal(false); + break; + case "Subscription": + switch (productPurchaseDTO.getSubscribeType()){ + case "Month": + productEnum = ProductEnum.MonthlySubscription; + break; + case "Year": + productEnum = ProductEnum.AnnualSubscription; + break; + /*case "Day": + productEnum = ProductEnum.DailySubscription; + break;*/ + default: + throw new BusinessException("unknown subscription type"); + } + break; + default: + throw new BusinessException("unknown product type"); + } + log.info("生成订单"); + OrderInfo orderInfo = orderInfoService.createOrderByProductId(productPurchaseDTO.getQuantity(), + PayTypeEnum.STRIPE.getType(), productEnum, request); + String payType; + if (productPurchaseDTO.getAutoRenewal()){ + payType = "recurring"; + }else { + payType = "one_time"; + } try { - //创建产品 - Map params = new HashMap<>(); - params.put("name", "AiDA credits purchase"); - Product product = Product.create(params); - String orderId = OrderNoUtils.getOrderNo(); - - BigDecimal actualAmount = new BigDecimal(Long.parseLong(CreditsEventsEnum.PRICE.getValue()) * 100); //stripe的默认单位是分,即传入的amount实际上小数点会被左移两位 - //给price绑定元数据并更新price用于检索 - Map metadata = new HashMap<>(); - metadata.put("orderId", orderId); - //创建价格 - Map priceParams = new HashMap<>(); - priceParams.put("metadata", metadata); //通过订单号关联用于检索price信息(可选) - priceParams.put("unit_amount", actualAmount.intValue()); - priceParams.put("currency", "HKD"); - priceParams.put("product", product.getId()); - Price price = Price.create(priceParams); + Long id = UserContext.getUserHolder().getId(); + com.ai.da.mapper.primary.entity.Account account = accountMapper.selectById(id); + // 获取或创建产品 + String productId = getProduct(productEnum.getName()); + // 获取或创建价格 + String priceId = getPrice(productEnum.getPrice(), productId, payType, productPurchaseDTO.getSubscribeType()); + // 获取或创建customer + String customerId = getCustomer(account.getUserName(), account.getUserEmail()); + // 获取自定义订单号 + String orderId = orderInfo.getOrderNo(); //创建支付信息得到url - SessionCreateParams params3 = SessionCreateParams.builder() - .setMode(SessionCreateParams.Mode.PAYMENT) - .setSuccessUrl(returnUrl)//可自定义成功页面 - .setPaymentIntentData(SessionCreateParams.PaymentIntentData.builder().setDescription("AiDA - " + orderId).build()) - .setLocale(account.getLanguage().equals("CHINESE_SIMPLIFIED") ? SessionCreateParams.Locale.ZH : SessionCreateParams.Locale.EN) - .addLineItem( - SessionCreateParams.LineItem.builder() - .setQuantity(Long.valueOf(quantity)) - .setPrice(price.getId()) - .build()) - .putMetadata("orderId", orderId) //通过订单号关联用于检索支付信息(可选) - .build(); - Session session = Session.create(params3); + // 一次性支付和周期扣款,需要区分mode: payment || subscription || setup + SessionCreateParams.Builder sessionBuilder = new SessionCreateParams.Builder(); + if (payType.equals("recurring")){ + sessionBuilder.setMode(SessionCreateParams.Mode.SUBSCRIPTION); + sessionBuilder.setSubscriptionData(SessionCreateParams.SubscriptionData.builder().setDescription("AiDA - " + orderId).build()); + }else { + sessionBuilder.setMode(SessionCreateParams.Mode.PAYMENT); + sessionBuilder.setPaymentIntentData(SessionCreateParams.PaymentIntentData.builder().setDescription("AiDA - " + orderId).build()); + // one-time 手动创建发票;订阅会自动创建invoice + sessionBuilder.setInvoiceCreation(SessionCreateParams.InvoiceCreation.builder().setEnabled(Boolean.TRUE).build()); + } + sessionBuilder.setCustomer(customerId); + sessionBuilder.setSuccessUrl(productPurchaseDTO.getReturnUrl());//可自定义成功页面 + sessionBuilder.setLocale(account.getLanguage().equals("CHINESE_SIMPLIFIED") ? SessionCreateParams.Locale.ZH : SessionCreateParams.Locale.EN); + sessionBuilder.addLineItem( + SessionCreateParams.LineItem.builder() + .setQuantity((long) productPurchaseDTO.getQuantity()) + .setPrice(priceId) + .build()); + sessionBuilder.putMetadata("orderId", orderId); //通过订单号关联用于检索支付信息(可选) + + Session session = Session.create(sessionBuilder.build()); log.info("sessionId:" + session.getId()); //退款方式1:拿到sessionId入库,退款的时候根据这个id找到PaymentIntent的id然后发起退款 // 更新order信息 orderInfoService.updateOrderNoById(orderInfo.getId(), orderId); - //记录支付日志 - paymentInfoService.createPaymentInfoForStripe(session); return session.getUrl(); } catch (Exception e) { log.error("创建支付会话出现异常:", e); @@ -114,6 +162,69 @@ public class StripeServiceImpl implements StripeService { return ""; } + // 获取产品ID + private String getProduct(String productName) throws StripeException { + Stripe.apiKey = privateKey; + // 1、获取所有的产品 + ProductCollection productCollection = Product.list(ProductListParams.builder().setActive(Boolean.TRUE).build()); + // 2、取一个指定名称的产品 + for (Product product : productCollection.getData()) { + if (product.getName().equals(productName)) { + return product.getId(); + } + } + // 3、在现有产品中没有找到指定产品,新建产品 + Map params = new HashMap<>(); + params.put("name", productName); + Product product = Product.create(params); + return product.getId(); + } + + /** + * 获取价格 + * + * @param priceValue 价格值 + * @param payType recurring || one_time + * @param recurringType monthly || yearly + */ + private String getPrice(Long priceValue, String productId, String payType, String recurringType) throws StripeException { + Stripe.apiKey = privateKey; + PriceCollection priceCollection = Price.list(PriceListParams.builder() + .setActive(Boolean.TRUE) + .setProduct(productId).build()); + for (Price price : priceCollection.getData()) { + // stripe的金额单位为分,所以这里需要 ×100 + if (price.getUnitAmount().equals(priceValue * 100) && price.getType().equals(payType)) { + return price.getId(); + } + } + + Price price = createPrice(priceValue, productId, payType, recurringType); + return price.getId(); + } + + private Price createPrice(Long priceValue, String productId, String payType, String recurringType) throws StripeException { + BigDecimal actualAmount = new BigDecimal(priceValue * 100); //stripe的默认单位是分,即传入的amount实际上小数点会被左移两位 + + PriceCreateParams.Builder priceCreateParams = new PriceCreateParams.Builder(); + priceCreateParams.setBillingScheme(PriceCreateParams.BillingScheme.PER_UNIT); + priceCreateParams.setCurrency("HKD"); + priceCreateParams.setProduct(productId); + priceCreateParams.setUnitAmount(actualAmount.longValue()); + + if (payType.equals("recurring")) { + PriceCreateParams.Recurring.Builder recurring = new PriceCreateParams.Recurring.Builder(); + // One of day, week, month or year. + recurring.setInterval(PriceCreateParams.Recurring.Interval.valueOf(recurringType.toUpperCase())); + // The number of intervals (specified in the interval attribute) between subscription billings. + // For example, interval=month and interval_count=3 bills every 3 months. + recurring.setIntervalCount(1L); + priceCreateParams.setRecurring(recurring.build()); + } + + return Price.create(priceCreateParams.build()); + } + @Override @Transactional(rollbackFor = Exception.class) public Boolean notify(HttpServletRequest request) { @@ -127,19 +238,19 @@ public class StripeServiceImpl implements StripeService { } catch (Exception e) { log.info("stripe 支付回调参数解析异常:errorMsg {}", e.getMessage()); log.info("request sigHeader = {}", sigHeader); - log.info("request body = {}", payload); + log.info("request body = {}", JSON.toJSONString(payload)); e.printStackTrace(); return Boolean.FALSE; } - Event event = null; + Event event; try { assert sigHeader != null; event = Webhook.constructEvent(payload, sigHeader, endpointSecret); } catch (SignatureVerificationException e) { - log.info("stripe 验签,获取事件异常, errorMsg=" + e.getMessage()); + log.info("stripe 验签,获取事件异常, errorMsg={}", e.getMessage()); log.info("request sigHeader = {}", sigHeader); - log.info("request body = {}", payload); + log.info("request body = {}", JSON.toJSONString(payload)); e.printStackTrace(); return Boolean.FALSE; } @@ -148,23 +259,119 @@ public class StripeServiceImpl implements StripeService { // Deserialize the nested object inside the event assert event != null; EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer(); - StripeObject stripeObject = null; + StripeObject stripeObject ; if (dataObjectDeserializer.getObject().isPresent()) { stripeObject = dataObjectDeserializer.getObject().get(); } else { log.info("stripe 验签失败!"); log.info("request sigHeader = {}", sigHeader); - log.info("request body = {}", payload); + log.info("request body = {}", JSON.toJSONString(payload)); return Boolean.FALSE; } log.info("stripe验签成功"); + Boolean response = Boolean.TRUE; - if (event.getType().equals("checkout.session.completed")) { + log.info("回调事件 {}", event.getType()); + if (stripeObject instanceof Session){ Session session = (Session) stripeObject; - processOrder(session); - } + if (event.getType().equals("checkout.session.completed")) { + processOrder(session); + }else if (event.getType().equals("checkout.session.expired")){ + String orderNo = session.getMetadata().get("orderId"); + // 会话过期 未支付 且之后没有支付成功的订单 + processExpiredOrder(orderNo); + } + } else if (stripeObject instanceof Subscription){ + Subscription subscription = (Subscription) stripeObject; + if (event.getType().equals("customer.subscription.created")){ + // 添加数据到t_subscription_info表 需记录订阅id。需要判断订阅的状态是否active吗 ?? + createSubscription(subscription); + log.info("创建连续订阅"); + } else if (event.getType().equals("customer.subscription.updated")){ + // 更新订阅信息 + SubscriptionInfo subscriptionInfo = updateSubscription(subscription); + log.info("订阅更新"); + if (subscription.getStatus().equals("active")){ + response = sendEmail(subscription.getId(), null); + } + // 续订支付失败,邮件通知用户 + if (subscription.getStatus().equals("past_due")){ + // 发送续订失败邮件 + response = sendRenewalFailEmail(null, subscription.getId(), subscriptionInfo.getOrderNo()); - return Boolean.TRUE; + } + } else if (event.getType().equals("customer.subscription.deleted")){ + SubscriptionInfo subscriptionInfo = updateSubscription(subscription); + log.info("用户 {} 取消连续订阅 {}", subscriptionInfo.getAccountId(), subscription.getId()); + if (subscriptionInfo.getCancelNotified() == (byte)0){ + log.info("取消订阅 邮件通知商家"); + response = sendEmail(subscription.getId(), "cancel"); + subscriptionInfo.setCancelNotified((byte)1); + subscriptionInfoMapper.updateById(subscriptionInfo); + } + + }/* else if (event.getType().equals("customer.subscription.paused")){ + updateSubscription(subscription); + } else if (event.getType().equals("customer.subscription.resumed")){ + updateSubscription(subscription); + log.info("用户订阅恢复"); + }*/ + } else if (stripeObject instanceof Invoice) { + Invoice invoice = (Invoice) stripeObject; + if (event.getType().equals("invoice.paid")) { + // 新增支付成功的信息,返回orderNo,表示,该回调第一次被记录 + PaymentInfo paymentInfo = paymentInfoService.createOrUpdatePaymentInfoForStripe(invoice); + + // 当前支付没有被通知时才需要发送通知邮件 + if (paymentInfo.getNotified().equals(0)) { + // 更新t_order_info中的total_fee,记录该订单的累计付款金额 + orderInfoService.updateTotalFeeByOrderNo(paymentInfo.getOrderNo()); + // 邮件通知商家和用户 + if (invoice.getBillingReason().equals("subscription_create")){ + response = sendEmail(invoice.getSubscription(), "new"); + } else if (invoice.getBillingReason().equals("subscription_cycle")){ + response = sendEmail(invoice.getSubscription(), "renewal"); + } + } + } else if (event.getType().equals("invoice.payment_failed")) { + // 更新支付信息 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("transaction_id", invoice.getId()); + PaymentInfo paymentInfo = paymentInfoService.getBaseMapper().selectOne(queryWrapper); + if (!Objects.isNull(paymentInfo)){ + String type = invoice.getBillingReason().equals("subscription_create") ? "new" : + invoice.getBillingReason().equals("subscription_cycle") ? "renewal" : invoice.getBillingReason(); + Gson gson = new Gson(); + String json = gson.toJson(invoice); + paymentInfo.setContent(json); + paymentInfo.setType(type); + paymentInfo.setHostedInvoiceUrl(invoice.getHostedInvoiceUrl()); + paymentInfoService.updateById(paymentInfo); + + // 发送续订失败邮件 + response = sendRenewalFailEmail(invoice.getId(), null, paymentInfo.getOrderNo()); + } + } + }else if (stripeObject instanceof Charge) { + Charge charge = (Charge) stripeObject; + String orderNo = charge.getDescription().replace("AiDA - ", ""); + OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderNo); + if (event.getType().equals("charge.failed")){ + // 添加支付信息 && 更新支付信息 + // 支付失败时,无法通过invoice_id获取支付方式,所以使用charge.failed回调添加支付信息 + paymentInfoService.createOrUpdatePaymentInfoForStripe(charge); + + orderInfo.setOrderStatus(OrderStatusEnum.FAILURE.getType()); + orderInfo.setNote(charge.getFailureMessage()); + orderInfoService.updateById(orderInfo); + }else if (event.getType().equals("charge.succeeded")){ + orderInfo.setOrderStatus(OrderStatusEnum.SUCCESS.getType()); + orderInfo.setNote(""); + orderInfoService.updateById(orderInfo); + } + } + log.info("回调事件 {} 处理完成", event.getType()); + return response; } public void processOrder(Session session) { @@ -186,37 +393,196 @@ public class StripeServiceImpl implements StripeService { orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.SUCCESS); log.info("Stripe 订单:{} 状态更新成功", orderId); - // 更新支付日志 - paymentInfoService.updatePaymentStatusById( - paymentInfoService.getPaymentInfoByOrderId(orderId).getId(), - session.getStatus(), - new Gson().toJson(session)); - - log.info("Stripe 订单:{} 支付信息状态更新成功", orderId); - float quantity = totalAmount / Float.parseFloat(CreditsEventsEnum.PRICE.getValue()); - // 更新积分 - creditsService.buyCredits(orderByOrderNo.getAccountId(), quantity); - // 添加积分变更记录 - creditsService.insertToCreditsDetail(orderByOrderNo.getAccountId(), - CreditsEventsEnum.BUY_CREDITS.getName() + "--Stripe", - String.valueOf((Long.parseLong(CreditsEventsEnum.BUY_CREDITS.getValue()) * quantity)), - "positive"); - log.info("用户:{} 积分信息更新成功", orderByOrderNo.getAccountId()); - }catch (Exception e){ + if (orderByOrderNo.getTitle().startsWith("积分购买")){ + float quantity = totalAmount / ProductEnum.CreditsProduct.getPrice(); + // 更新积分 + creditsService.buyCredits(orderByOrderNo.getAccountId(), quantity); + // 添加积分变更记录 + creditsService.insertToCreditsDetail(orderByOrderNo.getAccountId(), + CreditsEventsEnum.BUY_CREDITS.getName() + "--Stripe", + String.valueOf((Long.parseLong(CreditsEventsEnum.BUY_CREDITS.getValue()) * quantity)), + "positive", orderId); + log.info("用户:{} 积分信息更新成功", orderByOrderNo.getAccountId()); + } + } catch (Exception e) { log.info(e.getMessage()); } } + private void processExpiredOrder(String orderNo) { + // 支付失败 通知商家的条件 1、会话过期 2、支付失败 3、这个用户在这个支付失败后再无支付成功的订阅 + // 1、获取当前订单的支付状态 +// String orderNo = session.getMetadata().get("orderId"); + OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderNo); + // 2、确认订单状态为支付失败 + if (orderByOrderNo.getOrderStatus().equals(OrderStatusEnum.FAILURE.getType())) { + // 3、判断失败订单之后再无成功的订单 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("account_id", orderByOrderNo.getAccountId()); + queryWrapper.gt("create_time", orderByOrderNo.getCreateTime()); + queryWrapper.eq("order_status", OrderStatusEnum.SUCCESS.getType()); + queryWrapper.likeLeft("title", "Subscription"); + List orderInfos = orderInfoService.getBaseMapper().selectList(queryWrapper); + if (orderInfos.isEmpty()) { + // 4、判断当前订单有没有订阅信息 + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("order_no", orderNo); + SubscriptionInfo subscriptionInfo = subscriptionInfoMapper.selectOne(qw); + // 发送邮件通知商家用户支付失败 + if (Objects.isNull(subscriptionInfo) + || subscriptionInfo.getStatus().equals("incomplete") + || subscriptionInfo.getStatus().equals("incomplete_expired")) { + sendEmail(orderNo); + }else { + // todo 续订失败 应该不会走这里 + sendEmail(subscriptionInfo.getSubscriptionId(), "fail_renewal"); + } + } + } + } - public String refund(String amount, String orderId, String reason) { - Refund refund ; - RefundInfo refundByOrderNo = refundInfoService.createRefundByOrderNo(orderId, reason); + @Transactional(rollbackFor = Exception.class) + public SubscriptionInfo createSubscription(Subscription subscription){ + // 确认当前subscription是否已经记录 + SubscriptionInfo subscriptionInfo = getSubscriptionInfoBySubId(subscription.getId()); +// SubscriptionInfo subscriptionInfo = subscriptionInfoMapper.selectOne(qw); + if (Objects.isNull(subscriptionInfo)) { + String description = subscription.getDescription(); + String orderNo = description.replace("AiDA - ", ""); + OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderNo); + + // 从回调信息中获取recurring type + SubscriptionItem subscriptionItem = subscription.getItems().getData().get(0); + String interval = subscriptionItem.getPrice().getRecurring().getInterval(); + + subscriptionInfo = new SubscriptionInfo(); + subscriptionInfo.setAccountId(orderInfo.getAccountId()); + subscriptionInfo.setOrderNo(orderNo); + subscriptionInfo.setSubscriptionId(subscription.getId()); + subscriptionInfo.setType(interval); + subscriptionInfo.setStatus(subscription.getStatus()); + subscriptionInfo.setNextPayDate(DateUtil.changeTimeStampFormat(subscription.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy_EEEE)); + subscriptionInfo.setCurrentPeriodStart(subscription.getCurrentPeriodStart()); + subscriptionInfo.setCurrentPeriodEnd(subscription.getCurrentPeriodEnd()); + subscriptionInfo.setCreateTime(LocalDateTime.now()); + subscriptionInfoMapper.insert(subscriptionInfo); + + // 更新账号到期时间 + accountService.updateAccountValidity(subscriptionInfo.getAccountId(), subscriptionInfo.getCurrentPeriodEnd()); + + // 更新账号身份和积分 + accountService.updateUserRoleAndCredits(subscriptionInfo.getAccountId(), interval); + } + return subscriptionInfo; + } + + public SubscriptionInfo getSubscriptionInfoBySubId(String subId){ + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("subscription_id", subId); + + List subscriptionInfos = subscriptionInfoMapper.selectList(qw); + if (subscriptionInfos.size() == 1){ + return subscriptionInfos.get(0); + }else if (subscriptionInfos.size() > 1) { + // 如果新建了多个订阅,则筛选出状态为active的订单 + Optional activeSubscriptionInfo = subscriptionInfos.stream() + .filter(sub -> sub.getStatus().equals("active")) + .findFirst(); + + return activeSubscriptionInfo.orElseGet(() -> subscriptionInfos.get(0)); + }else { + return null; + } + } + + public SubscriptionInfo getLatestSubscriptionInfoByAccountId(Long accountId){ + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("account_id", accountId); + List subscriptionInfos = subscriptionInfoMapper.selectList(qw); + if (subscriptionInfos.isEmpty()){ + return null; + }else { + return subscriptionInfos.get(0); + } + } + + @Transactional(rollbackFor = Exception.class) + public SubscriptionInfo updateSubscription(Subscription subscription){ + // 获取当前是否有已经记录的subscriptionInfo + SubscriptionInfo subscriptionInfo = createSubscription(subscription); + // 用于标志数据有没有变动,避免在没有改动的情况下频繁的更新数据库 + boolean flag = false; + if (!subscriptionInfo.getStatus().equals(subscription.getStatus())){ + subscriptionInfo.setStatus(subscription.getStatus()); + flag = true; + } + if (!subscriptionInfo.getCurrentPeriodStart().equals(subscription.getCurrentPeriodStart())){ + subscriptionInfo.setCurrentPeriodStart(subscription.getCurrentPeriodStart()); + flag = true; + } + if (!subscriptionInfo.getCurrentPeriodEnd().equals(subscription.getCurrentPeriodEnd())){ + subscriptionInfo.setCurrentPeriodEnd(subscription.getCurrentPeriodEnd()); + subscriptionInfo.setNextPayDate(DateUtil.changeTimeStampFormat(subscription.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy_EEEE)); + // 更新账号到期时间 + accountService.updateAccountValidity(subscriptionInfo.getAccountId(), subscriptionInfo.getCurrentPeriodEnd()); + // 更新账号身份和积分 + accountService.updateUserRoleAndCredits(subscriptionInfo.getAccountId(), subscriptionInfo.getType()); + log.info("更新 {} 账号到期时间为:{}", subscriptionInfo.getAccountId(), DateUtil.changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy_EEEE)); + flag = true; + } + if (flag){ + subscriptionInfo.setUpdateTime(LocalDateTime.now()); + subscriptionInfoMapper.updateById(subscriptionInfo); + } + return subscriptionInfo; + } + + // 取消连续订阅 将订阅从pause状态转为cancel状态(使用定时器,定期检索DB中,过期且不续订的订阅) + public void cancelSubscription(String subscriptionId, String cancelReason) { + Stripe.apiKey = privateKey; + Long accountId = UserContext.getUserHolder().getId(); + log.info("用户 {} 申请取消连续订阅 {}", accountId, subscriptionId); + com.ai.da.mapper.primary.entity.Account account = accountMapper.selectById(accountId); + List subscriptions = getSubscription(account.getUserName(), account.getUserEmail()); + // 获取status = active的订阅 + subscriptions.forEach(subscription -> { + if (subscription.getId().equals(subscriptionId)) { + try { + Subscription cancel = subscription.cancel(); + cancel.getStatus(); + + // 更新数据库 + updateCancelReason(subscriptionId, cancelReason); + } catch (StripeException e) { + log.error("订阅 {} 取消失败, error message : {}", subscription.getId(), e.getMessage()); + } + } + }); + } + + public void cancelSubscriptionTemp(String subscriptionId) { + Stripe.apiKey = privateKey; + try { + log.info("申请取消连续订阅 {}", subscriptionId); + Subscription subscription = Subscription.retrieve(subscriptionId); + Subscription cancel = subscription.cancel(); + cancel.getStatus(); + } catch (StripeException e) { + log.error(e.getMessage()); +// throw new RuntimeException(e); + } + } + + public String refund(String amount, String orderNo, String reason) { + Refund refund; + RefundInfo refundByOrderNo = refundInfoService.createRefundByOrderNo(orderNo, reason); try { Stripe.apiKey = privateKey; + // todo transactionId不再是sessionId而是invoiceId,所以这里需要更新 // 根据orderId找到对应的sessionId - String sessionId = paymentInfoService.getPaymentInfoByOrderId(orderId).getTransactionId(); + String sessionId = paymentInfoService.getPaymentInfoByOrderNo(orderNo, "DESC").get(0).getTransactionId(); if (StringUtils.isNotEmpty(sessionId)) { //根据会话编号退款 Session session = Session.retrieve(sessionId); @@ -235,7 +601,7 @@ public class StripeServiceImpl implements StripeService { refund = Refund.create(params); log.info("根据会话编号退款成功"); - }else { + } else { log.error("当前订单不存在"); return "退款异常"; } @@ -250,7 +616,7 @@ public class StripeServiceImpl implements StripeService { if ("succeeded".equals(refund.getStatus())) { //进行数据库操作,修改状态为已退款(配合回调和退款查询确定退款成功) //更新订单状态 - orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.REFUND_SUCCESS); + orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.REFUND_SUCCESS); refundInfoService.updateRefundForPayPal( refundByOrderNo.getId(), @@ -259,11 +625,11 @@ public class StripeServiceImpl implements StripeService { AliPayTradeStateEnum.REFUND_SUCCESS.getType()); //退款成功 // 更新积分状态 - OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderId); - creditsService.creditsRefund(orderByOrderNo.getAccountId(), (int)(orderByOrderNo.getTotalFee() / Float.parseFloat(CreditsEventsEnum.PRICE.getValue()))); + OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderNo); + creditsService.creditsRefund(orderByOrderNo.getAccountId(), (int) (orderByOrderNo.getTotalFee() / Float.parseFloat(CreditsEventsEnum.PRICE.getValue())), orderNo); } else { //更新订单状态 - orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.REFUND_ABNORMAL); + orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.REFUND_ABNORMAL); //更新退款单 refundInfoService.updateRefundForPayPal( @@ -275,17 +641,18 @@ public class StripeServiceImpl implements StripeService { log.info("记录退款订单"); return "退款成功"; } - - public void checkOrderStatus(String orderNo){ + + public void checkOrderStatus(String orderNo) { Stripe.apiKey = privateKey; // 1、通过orderNo 查询sessionId - PaymentInfo paymentInfo = paymentInfoService.getPaymentInfoByOrderId(orderNo); + // todo transactionId不再是sessionId而是invoiceId,所以这里需要更新 + PaymentInfo paymentInfo = paymentInfoService.getPaymentInfoByOrderNo(orderNo, "DESC").get(0); try { Session session = Session.retrieve(paymentInfo.getTransactionId()); - if (Objects.isNull(session)){ + if (Objects.isNull(session)) { log.warn("核实订单未创建 ===> {}", orderNo); return; - } else if (session.getStatus().equals("open") || session.getStatus().equals("expired")){ + } else if (session.getStatus().equals("open") || session.getStatus().equals("expired")) { // 订单未支付 || 订单过期 ---> 均设置为超时未支付 log.info("订单超时未支付 ===> {}", orderNo); //更新本地订单状态 @@ -304,96 +671,483 @@ public class StripeServiceImpl implements StripeService { } - // 1、创建customer,获取customerId - // 2、创建客户支付方式 (从前端获取) - // 3、创建支付 paymentIntent - // 4、确认订单 - // 5、捕获金额(执行扣款操作) - public String createCustomer() throws StripeException { + public List getSubscription(String username, String userEmail) { + Stripe.apiKey = privateKey; + String customerId = null; + try { + customerId = getCustomer(username, userEmail); + SubscriptionCollection list = Subscription.list(SubscriptionListParams.builder() + .setCustomer(customerId).build()); + return list.getData(); + } catch (StripeException e) { + throw new RuntimeException(e); + } + } + + // 获取所有订阅 + public List getSubscriptionIds(String username, String userEmail) { + Stripe.apiKey = privateKey; + String customerId = null; + try { + customerId = getCustomer(username, userEmail); + SubscriptionCollection list = Subscription.list(SubscriptionListParams.builder() + .setCustomer(customerId).build()); + List data = list.getData(); + ArrayList subscriptionIds = new ArrayList<>(); + data.forEach(subscription -> { + subscriptionIds.add(subscription.getId()); + }); + return subscriptionIds; + } catch (StripeException e) { + throw new RuntimeException(e); + } + + } + + private String getCustomer(String username, String userEmail) throws StripeException { + CustomerCollection list = Customer.list(CustomerListParams.builder().setEmail(userEmail).build()); + List data = list.getData(); + if (!data.isEmpty()) { + return data.get(0).getId(); + } + return createCustomer(username, userEmail); + } + + private String createCustomer(String name, String userEmail) throws StripeException { Stripe.apiKey = privateKey; // Customer允许重复使用 CustomerCreateParams params = CustomerCreateParams.builder() - .setName("xp") - .setEmail("xupei3360@163.com") + .setName(name) + .setEmail(userEmail) .build(); - Customer customer = Customer.create(params); return customer.getId(); } - public String createPaymentMethod(String customerId) throws StripeException { - - Stripe.apiKey = privateKey; - - PaymentMethodCreateParams params = - PaymentMethodCreateParams.builder() - .setType(PaymentMethodCreateParams.Type.CARD) - .setCard( - // 测试中,不建议使用卡号,会不安全的异常,必须使用token(https://docs.stripe.com/testing?testing-method=tokens#visa) - PaymentMethodCreateParams.Token.builder().setToken("tok_visa").build() -// PaymentMethodCreateParams.CardDetails.builder() -// .setNumber("4242424242424242") -// .setExpMonth(8L) -// .setExpYear(2026L) -// .setCvc("314") -// .build() - ) - .build(); - - PaymentMethod paymentMethod = PaymentMethod.create(params); - return paymentMethod.getId(); + /** + * 使用连续订阅的订单,回调中没有paymentIntentId,所以通过invoiceId间接获取 + * @param invoiceId 发票Id + */ + public Map getPaymentMethodByInvoiceId(String invoiceId) { + try { + Stripe.apiKey = privateKey; + Invoice invoice = Invoice.retrieve(invoiceId); + if (!StringUtil.isNullOrEmpty(invoice.getPaymentIntent())){ + PaymentIntent paymentIntent = PaymentIntent.retrieve(invoice.getPaymentIntent()); + if (!StringUtil.isNullOrEmpty(paymentIntent.getPaymentMethod())){ + PaymentMethod paymentMethod = PaymentMethod.retrieve(paymentIntent.getPaymentMethod()); + return getPaymentMethod(paymentMethod.getId()); + } + } + HashMap resp = new HashMap<>(); + resp.put("paymentMethod", "N/A"); + resp.put("last4", "N/A"); + return resp; + } catch (StripeException e) { + throw new RuntimeException(e); + } } - public String createPaymentIntent(String paymentMethodId, String customerId) throws StripeException { + public Map getPaymentMethod(String paymentMethodId){ Stripe.apiKey = privateKey; + String paymentMethod = null; + String last4 = null; - Long amount = 600L; - PaymentIntentCreateParams params = - PaymentIntentCreateParams.builder() - .setAmount(amount) -// .setPaymentMethod(paymentMethodId) - .setCustomer(customerId) - .setCurrency("hkd") - .setAutomaticPaymentMethods( - PaymentIntentCreateParams.AutomaticPaymentMethods.builder() - .setEnabled(true) - .build() - ) - .build(); - - PaymentIntent paymentIntent = PaymentIntent.create(params); - return paymentIntent.getId(); + try { + PaymentMethod retrieve = PaymentMethod.retrieve(paymentMethodId); + switch (retrieve.getType()){ + case "alipay": + paymentMethod = "Alipay"; + last4 = "N/A"; + break; + case "bancontact": + paymentMethod = "BanContact"; + break; + case "card": + PaymentMethod.Card card = retrieve.getCard(); + String brand = card.getBrand(); + brand = brand.substring(0, 1).toUpperCase() + brand.substring(1); + paymentMethod = brand + " " + card.getFunding() + "card"; + last4 = card.getLast4(); + break; + case "eps": + PaymentMethod.Eps eps = retrieve.getEps(); + paymentMethod = eps.getBank(); + last4 = "N/A"; + break; + case "giropay": + paymentMethod = "GiroPay"; + last4 = "N/A"; + break; + case "ideal": + PaymentMethod.Ideal ideal = retrieve.getIdeal(); + paymentMethod = ideal.getBank(); + last4 = "N/A"; + break; + case "link": + paymentMethod = "Link"; + last4 = "N/A"; + break; + default: + paymentMethod = "N/A"; + last4 = "N/A"; + } + HashMap resp = new HashMap<>(); + resp.put("paymentMethod", paymentMethod); + resp.put("last4", last4); + return resp; + } catch (StripeException e) { + throw new RuntimeException(e); + } +// return null; } - public String confirmPaymentIntent(String clientSecret) throws StripeException { - Stripe.apiKey = privateKey; + public boolean sendEmail(String subscriptionId, String type) { + SubscriptionEmailParamsDTO emailParamsDTO = new SubscriptionEmailParamsDTO(); + QueryWrapper qwSI = new QueryWrapper<>(); + qwSI.eq("subscription_id", subscriptionId); + SubscriptionInfo subscriptionInfo = subscriptionInfoMapper.selectOne(qwSI); + if (Objects.isNull(subscriptionInfo)) { + return false; + } + QueryWrapper qwPI = new QueryWrapper<>(); + qwPI.eq("order_no", subscriptionInfo.getOrderNo()).orderByDesc("id"); + List paymentInfos = paymentInfoMapper.selectList(qwPI); + if (paymentInfos.isEmpty()) { + return false; + } + PaymentInfo paymentInfo = paymentInfos.get(0); + if (StringUtil.isNullOrEmpty(type)){ + // 如果没有传入type,则使用paymentInfo中记录的类型 + // (其实这里也可以通过invoiceId查询stripe,但是记录在自己的db中可以不用每次都查,且方便查看) + type = StringUtil.isNullOrEmpty(paymentInfo.getType()) ? "new" : paymentInfo.getType(); + } + if (!type.equals("reminder") && !type.equals("cancel") && paymentInfo.getNotified() == 1){ + // 已经邮件通知过,直接返回 + return true; + } - PaymentIntent resource = PaymentIntent.retrieve(clientSecret); + com.ai.da.mapper.primary.entity.Account account = accountMapper.selectById(subscriptionInfo.getAccountId()); + String userName = account.getUserName(); + String language = account.getLanguage(); + OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(subscriptionInfo.getOrderNo()); - PaymentIntentConfirmParams params = - PaymentIntentConfirmParams.builder() - .setPaymentMethod("pm_card_visa") - .setReturnUrl("https://www.example.com") - .build(); + emailParamsDTO.setUsername(userName); + emailParamsDTO.setOrderId(paymentInfo.getId().toString()); + emailParamsDTO.setOrderRef("\"" + orderListLink + paymentInfo.getId().toString() + "\""); + emailParamsDTO.setCreateDate(String.valueOf(paymentInfo.getCreateTime()).replace("T", " ")); + emailParamsDTO.setQuantity(String.valueOf(1)); + emailParamsDTO.setTotalFee(paymentInfo.getPayerTotal().toString()); + emailParamsDTO.setLastOrderDate(DateUtil.changeTimeStampFormat(subscriptionInfo.getCurrentPeriodStart(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy)); + emailParamsDTO.setEndOfPrepaidTerm(DateUtil.changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy)); + setSubscriptionParams(paymentInfo, subscriptionInfo, orderByOrderNo, emailParamsDTO); - PaymentIntent paymentIntent = resource.confirm(params); - return paymentIntent.getId(); + SendEmailUtil.subscriptionEmailReminder(type, emailParamsDTO, language, account.getUserEmail()); + + // 邮件通知成功后,更新标志 + if (!type.equals("reminder") && !type.equals("cancel")){ + PaymentInfo payment = new PaymentInfo(); + payment.setId(paymentInfo.getId()); + payment.setNotified(1); + payment.setUpdateTime(LocalDateTime.now()); + paymentInfoMapper.updateById(payment); + } + return true; } - public String capturePaymentIntent(String clientSecret) throws StripeException { + public boolean sendEmail(String orderNo){ + SubscriptionEmailParamsDTO emailParamsDTO = new SubscriptionEmailParamsDTO(); + OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderNo); + com.ai.da.mapper.primary.entity.Account account = accountMapper.selectById(orderInfo.getAccountId()); + String userName = account.getUserName(); + String language = account.getLanguage(); + QueryWrapper qwPI = new QueryWrapper<>(); + qwPI.eq("order_no", orderNo); + List paymentInfos = paymentInfoMapper.selectList(qwPI); + if (paymentInfos.isEmpty()) { + return false; + } + PaymentInfo paymentInfo = paymentInfos.get(0); + emailParamsDTO.setUsername(userName); + emailParamsDTO.setOrderId(paymentInfo.getId().toString()); + emailParamsDTO.setCreateDate(String.valueOf(paymentInfo.getCreateTime()).replace("T", " ")); + emailParamsDTO.setQuantity(String.valueOf(1)); + emailParamsDTO.setTotalFee(paymentInfo.getPayerTotal().toString()); + emailParamsDTO.setFailMessage(orderInfo.getNote()); + emailParamsDTO.setPaymentMethod(paymentInfo.getPaymentMethod()); + emailParamsDTO.setLast4(paymentInfo.getLast4()); + + SendEmailUtil.subscriptionEmailReminder("fail_new", emailParamsDTO, language, account.getUserEmail()); + + // 邮件通知成功后,更新标志 + PaymentInfo payment = new PaymentInfo(); + payment.setId(paymentInfo.getId()); + payment.setNotified(1); + payment.setUpdateTime(LocalDateTime.now()); + paymentInfoMapper.updateById(payment); + return true; + } + + public boolean sendRenewalFailEmail(String invoiceId, String subscriptionId, String orderNo){ + // 1、确认当前订单最后一笔支付为fail + // 更新支付信息 + PaymentInfo paymentInfo; + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (StringUtil.isNullOrEmpty(invoiceId)){ + queryWrapper.eq("order_no", orderNo).orderByDesc("id"); + List paymentInfos = paymentInfoService.getBaseMapper().selectList(queryWrapper); + if (paymentInfos.isEmpty() || !paymentInfos.get(0).getTradeState().equals("failed")){ + return false; + }else { + paymentInfo = paymentInfos.get(0); + } + }else { + queryWrapper.eq("transaction_id", invoiceId); + paymentInfo = paymentInfoService.getBaseMapper().selectOne(queryWrapper); + if (Objects.isNull(paymentInfo) + || !paymentInfo.getTradeState().equals("failed") + || paymentInfo.getNotified().equals(1)){ + return false; + } + } + + // 2、确认当前订阅的状态为past_due + SubscriptionInfo subscriptionInfo; + QueryWrapper qwSI = new QueryWrapper<>(); + if (StringUtil.isNullOrEmpty(subscriptionId)){ + qwSI.eq("order_no", orderNo); + }else { + qwSI.eq("subscription_id", subscriptionId); + } + subscriptionInfo = subscriptionInfoMapper.selectOne(qwSI); + if (Objects.isNull(subscriptionInfo) || !subscriptionInfo.getStatus().equals("past_due")){ + return false; + } + + // 3、组参数 + com.ai.da.mapper.primary.entity.Account account = accountMapper.selectById(subscriptionInfo.getAccountId()); + String userName = account.getUserName(); + String language = account.getLanguage(); + OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(subscriptionInfo.getOrderNo()); + SubscriptionEmailParamsDTO emailParamsDTO = new SubscriptionEmailParamsDTO(); + emailParamsDTO.setUsername(userName); + emailParamsDTO.setOrderId(paymentInfo.getId().toString()); + emailParamsDTO.setCreateDate(String.valueOf(paymentInfo.getCreateTime()).replace("T", " ")); + emailParamsDTO.setQuantity(String.valueOf(1)); + emailParamsDTO.setTotalFee(paymentInfo.getPayerTotal().toString()); + setSubscriptionParams(paymentInfo, subscriptionInfo, orderByOrderNo, emailParamsDTO); + + // 4、发邮件 + SendEmailUtil.subscriptionEmailReminder("fail_renewal", emailParamsDTO, language, account.getUserEmail()); + + PaymentInfo payment = new PaymentInfo(); + payment.setId(paymentInfo.getId()); + payment.setNotified(1); + payment.setUpdateTime(LocalDateTime.now()); + paymentInfoMapper.updateById(payment); + return true; + } + + private void setSubscriptionParams(PaymentInfo paymentInfo, SubscriptionInfo subscriptionInfo, OrderInfo orderByOrderNo, SubscriptionEmailParamsDTO emailParamsDTO) { + emailParamsDTO.setPaymentMethod(paymentInfo.getPaymentMethod()); + emailParamsDTO.setLast4(paymentInfo.getLast4()); + emailParamsDTO.setSubscriptionId(subscriptionInfo.getId().toString()); + emailParamsDTO.setFailMessage(orderByOrderNo.getNote()); + emailParamsDTO.setSubscriptionType(subscriptionInfo.getType()); + emailParamsDTO.setStartDate(DateUtil.changeTimeStampFormat(orderByOrderNo.getCreateTime())); + emailParamsDTO.setNextPayDate(DateUtil.changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy)); + emailParamsDTO.setRenewalTime(DateUtil.changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy)); + } + + public void subscriptionReminder(){ + // 提前7天的 00:00:00 和 23:59:59 + LocalDateTime startOfDay = LocalDateTime.now().plusDays(7).toLocalDate().atStartOfDay(); + LocalDateTime endOfDay = LocalDateTime.now().plusDays(7).toLocalDate().atTime(23, 59, 59); + + // 转为时间戳 + long startTimestamp = startOfDay.toEpochSecond(ZoneOffset.UTC); + long endTimestamp = endOfDay.toEpochSecond(ZoneOffset.UTC); + + QueryWrapper qw = new QueryWrapper<>(); + qw.ge("current_period_end", startTimestamp); + qw.lt("current_period_end", endTimestamp); + qw.eq("status", "active"); + + List subscriptionInfos = subscriptionInfoMapper.selectList(qw); + for (SubscriptionInfo subscriptionInfo : subscriptionInfos) { + boolean b = sendEmail(subscriptionInfo.getSubscriptionId(), "reminder"); + if (b) log.info("提前7天向用户 {} 发送续订通知邮件", subscriptionInfo.getAccountId()); + } + } + + public void checkSubscriptionExpiration(){ + long epochSecond = Instant.now().getEpochSecond(); + QueryWrapper qw = new QueryWrapper<>(); + qw.lt("current_period_end", epochSecond); + qw.eq("status", "active"); + List subscriptionInfos = subscriptionInfoMapper.selectList(qw); + + for (SubscriptionInfo subscriptionInfo : subscriptionInfos) { + subscriptionInfo.setStatus("expired"); + subscriptionInfo.setUpdateTime(LocalDateTime.now()); + subscriptionInfoMapper.updateById(subscriptionInfo); + log.info("用户 {} 的订阅 {} 已过期", subscriptionInfo.getAccountId(), subscriptionInfo.getOrderNo()); + } + } + + // 新建一个订阅 使用不会成功的付款方式(仅供测试使用) + public String createSubscriptionTemp(String name, String email){ Stripe.apiKey = privateKey; + try { + OrderInfo orderInfo = orderInfoService.createOrderByProductId(1, PayTypeEnum.STRIPE.getType(), ProductEnum.DailySubscription, null); - PaymentIntent resource = PaymentIntent.retrieve(clientSecret); +// String customerId = getCustomer(name, email); + String paymentMethodCode = "pm_card_mastercard"; + PaymentMethod paymentMethod = PaymentMethod.retrieve(paymentMethodCode); - PaymentIntentCaptureParams params = PaymentIntentCaptureParams.builder().build(); + String customerId = getCustomer(name, email); + log.info("customerId: {}", customerId); - PaymentIntent paymentIntent = resource.capture(params); + PaymentMethodAttachParams attachParams = PaymentMethodAttachParams.builder() + .setCustomer(customerId) + .build(); + paymentMethod.attach(attachParams); + + // 设置默认付款方式 + Customer updatedCustomer = Customer.retrieve(customerId); + CustomerUpdateParams params = CustomerUpdateParams.builder() + .setInvoiceSettings( + CustomerUpdateParams.InvoiceSettings.builder() + .setDefaultPaymentMethod(paymentMethod.getId()) + .build()) + .build(); + updatedCustomer.update(params); + + // 3. 创建订阅 + SubscriptionCreateParams subscriptionParams = SubscriptionCreateParams.builder() + .setCustomer(customerId) + .addItem( + SubscriptionCreateParams.Item.builder() + .setPrice("price_1QFXkf02n1TEydyNtA4TQ3Yz") // 替换为实际的价格 ID + .build()) + .setDescription("AiDA - " + orderInfo.getOrderNo()) + .build(); + Subscription subscription = Subscription.create(subscriptionParams); + + // 打印订阅 ID + System.out.println("Subscription created: " + subscription.getId()); + return subscription.getId(); + } catch (StripeException e) { + throw new RuntimeException(e); + } + } + + public String changeCustomerPayment(String name, String email){ + Stripe.apiKey = privateKey; + String paymentMethodCode = "pm_card_chargeCustomerFail"; + try { + PaymentMethod paymentMethod = PaymentMethod.retrieve(paymentMethodCode); + + String customerId = getCustomer(name, email); + log.info("customerId: {}", customerId); + + // 附加支付方式到客户 + PaymentMethodAttachParams attachParams = PaymentMethodAttachParams.builder() + .setCustomer(customerId) + .build(); + paymentMethod.attach(attachParams); + // 更新客户的默认支付方式 + CustomerUpdateParams params = CustomerUpdateParams.builder() + .setInvoiceSettings( + CustomerUpdateParams.InvoiceSettings.builder() + .setDefaultPaymentMethod(paymentMethod.getId()) + .build() + ) + .build(); + + // 更新客户信息 + Customer customer = Customer.retrieve(customerId); + customer.update(params); + + return paymentMethod.getId(); + + } catch (StripeException e) { + throw new RuntimeException(e); + } + } + + public List> getCustomerPaymentMethod(String name, String email){ + Stripe.apiKey = privateKey; + try { + String customerId = getCustomer(name, email); + Customer customer = Customer.retrieve(customerId); + PaymentMethodCollection paymentMethodCollection = customer.listPaymentMethods(); + List data = paymentMethodCollection.getData(); + ArrayList> resp = new ArrayList<>(); + data.forEach(paymentMethod -> { + Map map = new HashMap<>(); + if (paymentMethod.getType().equals("card")){ + map.put(paymentMethod.getId(),paymentMethod.getCard().getLast4()); + }else { + map.put(paymentMethod.getId(),null); + } + resp.add(map); + }); + + return resp; + // 方向: 向用户添加了多种付款方式,更改默认的付款方式后,默认付款方式付款失败后是否自动使用其他付款方式付款? + // 如果是的,则需要删除能成功的付款方式,保留唯一失败的付款方式进行续订付款失败测试 + } catch (StripeException e) { + throw new RuntimeException(e); + } + + } + + public String detachCustomerAllPaymentMethod(String name, String email){ + Stripe.apiKey = privateKey; + // 方向: 向用户添加了多种付款方式,更改默认的付款方式后,默认付款方式付款失败后是否自动使用其他付款方式付款? + // 如果是的,则需要删除能成功的付款方式,保留唯一失败的付款方式进行续订付款失败测试 + try { + String customerId = getCustomer(name, email); + Customer customer = Customer.retrieve(customerId); + PaymentMethodCollection paymentMethodCollection = customer.listPaymentMethods(); + List data = paymentMethodCollection.getData(); + data.forEach(paymentMethod -> { + try { + PaymentMethod retrieve = PaymentMethod.retrieve(paymentMethod.getId()); + PaymentMethodDetachParams params = PaymentMethodDetachParams.builder().build(); + retrieve.detach(params); + } catch (StripeException e) { + throw new RuntimeException(e); + } + }); + } catch (StripeException e) { + throw new RuntimeException(e); + } return null; } + public void updateCancelReason(String subscriptionId, String reason){ + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("subscription_id", subscriptionId); + SubscriptionInfo subscriptionInfo = subscriptionInfoMapper.selectOne(qw); + if (!Objects.isNull(subscriptionInfo)) { + subscriptionInfo.setCancelReason(reason); + subscriptionInfoMapper.updateById(subscriptionInfo); + } + } + +// public String getIp(HttpServletRequest request) { +// String ipAddress = RequestInfoUtil.getIpAddress(request); +// if (!StringUtil.isNullOrEmpty(ipAddress)) { +// return getIPLocation(ipAddress); +// } +// +// return request.getRemoteAddr(); +// } } diff --git a/src/main/java/com/ai/da/service/impl/SuperResolutionServiceImpl.java b/src/main/java/com/ai/da/service/impl/SuperResolutionServiceImpl.java index 03ed3d69..eda65b46 100644 --- a/src/main/java/com/ai/da/service/impl/SuperResolutionServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/SuperResolutionServiceImpl.java @@ -149,7 +149,7 @@ public class SuperResolutionServiceImpl extends ServiceImpl implements Ta public List getTags(String tagPrefix){ // 1、根据tag前缀,查询 QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.likeRight("tag_name", tagPrefix); + if (!StringUtil.isNullOrEmpty(tagPrefix)) { + queryWrapper.like("tag_name", tagPrefix); + } + queryWrapper.orderByDesc("id").last("limit 10"); // 需返回标签内容和id return baseMapper.selectList(queryWrapper); diff --git a/src/main/java/com/ai/da/service/impl/UserLikeGroupServiceImpl.java b/src/main/java/com/ai/da/service/impl/UserLikeGroupServiceImpl.java index 250bf31e..1b7698eb 100644 --- a/src/main/java/com/ai/da/service/impl/UserLikeGroupServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/UserLikeGroupServiceImpl.java @@ -5,11 +5,14 @@ import com.ai.da.common.config.exception.BusinessException; import com.ai.da.common.constant.CommonConstant; import com.ai.da.common.context.UserContext; import com.ai.da.common.enums.CreditsEventsEnum; +import com.ai.da.common.response.ResultEnum; import com.ai.da.common.utils.*; import com.ai.da.mapper.primary.*; import com.ai.da.mapper.primary.entity.*; import com.ai.da.mapper.secondary.AttributeRetrievalMapper; +import com.ai.da.mapper.secondary.entity.AttributeRecognitionJSON; import com.ai.da.model.dto.PortfolioDTO; +import com.ai.da.model.dto.ProductImageInitializeDTO; import com.ai.da.model.dto.ProductImageLikeDTO; import com.ai.da.model.dto.ToProductImageDTO; import com.ai.da.model.vo.*; @@ -82,6 +85,10 @@ public class UserLikeGroupServiceImpl extends ServiceImpl userLikeVOS = userLikeService.getGroupDetail(userGroupId); String sex = null; + + QueryWrapper userLikeSortQw = new QueryWrapper<>(); + userLikeSortQw.lambda().eq(UserLikeSort::getUserLikeGroupId, userGroupId); + List userLikeSortList = userLikeSortMapper.selectList(userLikeSortQw); + if (CollectionUtil.isEmpty(userLikeSortList)) { + Integer sort = 1; + for (UserLikeVO userLikeVO : userLikeVOS) { + UserLikeSort userLikeSort = new UserLikeSort(); + userLikeSort.setUserLikeId(userLikeVO.getId()); + userLikeSort.setUserLikeGroupId(userGroupId); + userLikeSort.setSort(sort); + userLikeSortMapper.insert(userLikeSort); + sort ++; + } + } + userLikeVOS.forEach(o -> { TDesignPythonOutfit tDesignPythonOutfit1 = designPythonOutfitMapper.selectById(o.getDesignOutfitId()); o.setUrl(tDesignPythonOutfit1.getDesignUrl()); @@ -162,6 +185,15 @@ public class UserLikeGroupServiceImpl extends ServiceImpl userLikeSortQueryWrapper = new QueryWrapper<>(); + userLikeSortQueryWrapper.lambda().eq(UserLikeSort::getUserLikeId, o.getId()); + List userLikeSorts = userLikeSortMapper.selectList(userLikeSortQueryWrapper); + if (CollectionUtil.isNotEmpty(userLikeSorts)) { + UserLikeSort userLikeSort = userLikeSorts.get(0); + o.setSort(userLikeSort.getSort()); + o.setUserLikeSortId(userLikeSort.getId()); + } }); UserLikeCollectionVO userLikeCollection = collectionService.chooseCollection(group.getCollectionId()); Integer beenPublished = 0; @@ -243,7 +275,7 @@ public class UserLikeGroupServiceImpl extends ServiceImpl update t_account - set valid_end_time = null, is_trial = 0, credits = 0, system_user = 0, update_date = #{date} + set is_trial = 0, credits = 0, system_user = 0 where id = #{id} diff --git a/src/main/resources/mapper/primary/AffiliateIncomeMapper.xml b/src/main/resources/mapper/primary/AffiliateIncomeMapper.xml new file mode 100644 index 00000000..bac4a547 --- /dev/null +++ b/src/main/resources/mapper/primary/AffiliateIncomeMapper.xml @@ -0,0 +1,36 @@ + + + + + + + + + diff --git a/src/main/resources/mapper/primary/AffiliateMapper.xml b/src/main/resources/mapper/primary/AffiliateMapper.xml new file mode 100644 index 00000000..e2637ec0 --- /dev/null +++ b/src/main/resources/mapper/primary/AffiliateMapper.xml @@ -0,0 +1,75 @@ + + + + + + + + + + diff --git a/src/main/resources/mapper/primary/PaymentInfoMapper.xml b/src/main/resources/mapper/primary/PaymentInfoMapper.xml new file mode 100644 index 00000000..29a82d72 --- /dev/null +++ b/src/main/resources/mapper/primary/PaymentInfoMapper.xml @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + diff --git a/src/main/resources/messages_en.properties b/src/main/resources/messages_en.properties index 529b7bcf..04a4260a 100644 --- a/src/main/resources/messages_en.properties +++ b/src/main/resources/messages_en.properties @@ -148,6 +148,7 @@ remaining.modifications=Remaining modifications are 0 you.have.participated.in.the.event=You have participated in the event. only.original.works.can.participate.in.the.event=Sorry, only original works can participate in the event. remaining.credits.insufficient=Your remaining credits are insufficient for this generation. Please recharge. +you.haven't.subscribed.to.any.products.yet=You haven't subscribed to any products yet # 可能会报异常 # Informative: diff --git a/src/main/resources/messages_zh.properties b/src/main/resources/messages_zh.properties index c9ed6c67..d7c144ab 100644 --- a/src/main/resources/messages_zh.properties +++ b/src/main/resources/messages_zh.properties @@ -143,6 +143,7 @@ remaining.modifications=剩余修改次数为0 you.have.participated.in.the.event=您已经参与活动。 only.original.works.can.participate.in.the.event=抱歉,只有原创作品能参与活动。 remaining.credits.insufficient=您的剩余积分不够本次生成消耗,请充值 +you.haven't.subscribed.to.any.products.yet=您还未订阅任何产品 # 可能会报异常 # Informative: diff --git a/src/main/resources/payment.properties b/src/main/resources/payment.properties index 56e5a315..a4086434 100644 --- a/src/main/resources/payment.properties +++ b/src/main/resources/payment.properties @@ -1,12 +1,12 @@ ##### PayPal # developer-sandbox-xp -#paypal.client-id=ATbaebYi7-GXWRWJqwRLYMzKEbwjh4BFRqD4Y13i4lZq0rplWIM_IpPrtPKpdkAt_KrPXd6IJTwsDqa5 -#paypal.client-secret=EHWWJqGmmbfjLXqCUpGrvxRYBPPtWvA3hR5ZaAyHlGSVJiHoQPS8skbNaJ9h39VObnchUbgiY2pPu__s -#paypal.receiver.email=sb-ukxfk29608925@business.example.com -#paypal.mode=sandbox -# local -#paypal.webhook_id=31797347YC028794L +paypal.client-id=ATbaebYi7-GXWRWJqwRLYMzKEbwjh4BFRqD4Y13i4lZq0rplWIM_IpPrtPKpdkAt_KrPXd6IJTwsDqa5 +paypal.client-secret=EHWWJqGmmbfjLXqCUpGrvxRYBPPtWvA3hR5ZaAyHlGSVJiHoQPS8skbNaJ9h39VObnchUbgiY2pPu__s +paypal.receiver.email=sb-ukxfk29608925@business.example.com +paypal.mode=sandbox +## local +paypal.webhook_id=31797347YC028794L # dev #paypal.webhook_id=51V87014T6406322F @@ -18,22 +18,23 @@ #paypal.webhook_id=1WH327112B602422N # aida-live-kim -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=1D107312EX592781K +#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=1D107312EX592781K ##### Stripe # developer -#stripe.private-key=sk_test_51P4ZZL02n1TEydyN8qQHjOA9imsFU7Oxs2HMHGy2urHnnQgSHnZuu5vVP6pKhEACwUpsKNyrbZpdcg5TJWJLRHcY008dEO1fn2 -#stripe.webhook-sign-secret=whsec_e0dBiJngx6qqgJj6yPyJ2A9ouh1Cjv5w +stripe.private-key=sk_test_51P4ZZL02n1TEydyN8qQHjOA9imsFU7Oxs2HMHGy2urHnnQgSHnZuu5vVP6pKhEACwUpsKNyrbZpdcg5TJWJLRHcY008dEO1fn2 +stripe.webhook-sign-secret=whsec_e0dBiJngx6qqgJj6yPyJ2A9ouh1Cjv5w +#stripe.webhook-sign-secret=whsec_TJcMSnAkh4uktrNY1M6Iy8XaVze4Rzqm # kim - test #stripe.private-key=sk_test_51LwPrxH7nPZ8bkrNj67TFD7sxucaTANs1lf0KGSu1QSJfxYXcnigq2wTaZyZzST7y0fMbhhvaJZ4LjjFhr95M83a00eXrmOTL0 #stripe.webhook-sign-secret=whsec_GoyVEAaBtuGD5Rt55z83JnPnLDAZTN3u # kim - live -stripe.private-key=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 -stripe.webhook-sign-secret=whsec_hhGDgdelQRHSg4LmChtQe41crj41eb11 \ No newline at end of file +#stripe.private-key=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 +#stripe.webhook-sign-secret=whsec_hhGDgdelQRHSg4LmChtQe41crj41eb11 \ No newline at end of file