diff --git a/pom.xml b/pom.xml
index 6f15bdc..55be82c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,6 +18,7 @@
UTF-8
+ 17
@@ -62,6 +63,12 @@
+
+ com.alipay.sdk
+ alipay-sdk-java
+ 4.40.806.ALL
+
+
junit
junit
diff --git a/src/main/java/com/ping/study/config/AlipayConfig.java b/src/main/java/com/ping/study/config/AlipayConfig.java
new file mode 100644
index 0000000..95e092e
--- /dev/null
+++ b/src/main/java/com/ping/study/config/AlipayConfig.java
@@ -0,0 +1,27 @@
+package com.ping.study.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "alipay")
+public class AlipayConfig {
+
+ private String gatewayUrl;
+
+ private String appId;
+
+ private String privateKey;
+
+ private String alipayPublicKey;
+
+ private String notifyUrl;
+
+ private String charset;
+
+ private String format;
+
+ private String signType;
+}
diff --git a/src/main/java/com/ping/study/config/WebMvc.java b/src/main/java/com/ping/study/config/WebMvc.java
index 1f9d1b9..6f6456e 100644
--- a/src/main/java/com/ping/study/config/WebMvc.java
+++ b/src/main/java/com/ping/study/config/WebMvc.java
@@ -17,7 +17,7 @@ public class WebMvc implements WebMvcConfigurer {
.allowedOrigins("http://jrs77.xyz","https://jrs77.xyz",
"http://localhost:5173", "https://nba.1024x.icu",
"http://nba.1024x.icu","http://167.253.156.235:9006",
- "http://116.62.173.2:9001")
+ "http://116.62.173.2:9001","http://dns.1024x.icu:9005")
.allowedMethods("GET","POST","PUT","DELETE","OPTIONS")
.allowedHeaders("X-Timestamp","X-Sign","*")
.allowCredentials(true) // 若不用 Cookie 也可为 false
diff --git a/src/main/java/com/ping/study/controller/NbaController.java b/src/main/java/com/ping/study/controller/NbaController.java
index 34083aa..179da2a 100644
--- a/src/main/java/com/ping/study/controller/NbaController.java
+++ b/src/main/java/com/ping/study/controller/NbaController.java
@@ -143,10 +143,10 @@ public class NbaController {
if (loginUser == null){
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("请先登录");
}
- log.info("{}:获取直播链接开始",loginUser.getEmail());
+// log.info("{}:获取直播链接开始",loginUser.getEmail());
String ip = userUtil.getClientIp(request);
String url = urlsService.findUrlByGameIdAndType(gameId, playType);
-// log.info("来自ip:{} 用户:{}:获取直播链接成功:{}",ip,loginUser.getEmail(),url);
+ log.info("来自ip:{} 用户:{}:获取直播链接成功:{}",ip,loginUser.getEmail(),url);
return ResponseEntity.ok(url);
}
diff --git a/src/main/java/com/ping/study/controller/pay/AlipayController.java b/src/main/java/com/ping/study/controller/pay/AlipayController.java
new file mode 100644
index 0000000..c2faa97
--- /dev/null
+++ b/src/main/java/com/ping/study/controller/pay/AlipayController.java
@@ -0,0 +1,74 @@
+package com.ping.study.controller.pay;
+
+import com.ping.study.config.AlipayConfig;
+import com.ping.study.model.dto.pay.CreateRewardOrderDto;
+import com.ping.study.model.vo.pay.AlipayQrCodeVo;
+import com.ping.study.model.vo.pay.PayOrderStatusVo;
+import com.ping.study.pojo.NbaUser;
+import com.ping.study.service.pay.AlipayService;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpSession;
+import jakarta.validation.Valid;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/pay/alipay")
+@Slf4j
+public class AlipayController {
+
+ private final AlipayService alipayService;
+ private final AlipayConfig alipayConfig;
+
+ public AlipayController(AlipayService alipayService, AlipayConfig alipayConfig) {
+ this.alipayService = alipayService;
+ this.alipayConfig = alipayConfig;
+ }
+
+ /**
+ * 创建支付宝打赏二维码订单。
+ * 当前允许匿名打赏;如果用户已登录,会自动绑定 userId。
+ */
+ @PostMapping("/reward/create")
+ public AlipayQrCodeVo createRewardOrder(@Valid @RequestBody CreateRewardOrderDto dto, HttpSession session) throws Exception {
+ NbaUser loginUser = (NbaUser) session.getAttribute("loginUser");
+ return alipayService.createRewardOrder(dto, loginUser);
+ }
+
+ /**
+ * 支付宝异步通知。
+ * 注意:该接口必须公网可访问,且不能要求登录。支付宝要求处理成功后返回纯文本 success。
+ */
+ @PostMapping("/notify")
+ public String notify(HttpServletRequest request) throws Exception {
+ Map params = new HashMap<>();
+ Map requestParams = request.getParameterMap();
+ for (Map.Entry entry : requestParams.entrySet()) {
+ String[] values = entry.getValue();
+ if (values != null && values.length > 0) {
+ params.put(entry.getKey(), values[0]);
+ }
+ }
+ log.info("收到支付宝异步通知,orderNo={}, tradeStatus={}", params.get("out_trade_no"), params.get("trade_status"));
+ return alipayService.handleNotify(params);
+ }
+
+ /**
+ * 前端展示二维码后可轮询该接口获取支付状态。
+ */
+ @GetMapping("/status/{orderNo}")
+ public PayOrderStatusVo queryOrderStatus(@PathVariable String orderNo) {
+ return alipayService.queryOrderStatus(orderNo);
+ }
+
+ /**
+ * 用于前端或部署后快速确认 notifyUrl 是否配置正确。
+ */
+ @GetMapping("/config/notify-url")
+ public String notifyUrl() {
+ return alipayConfig.getNotifyUrl();
+ }
+}
diff --git a/src/main/java/com/ping/study/controller/user/UserController.java b/src/main/java/com/ping/study/controller/user/UserController.java
index 6c3b940..7a3d26a 100644
--- a/src/main/java/com/ping/study/controller/user/UserController.java
+++ b/src/main/java/com/ping/study/controller/user/UserController.java
@@ -1,9 +1,12 @@
package com.ping.study.controller.user;
+import com.ping.study.mapper.PayOrderMapper;
import com.ping.study.model.dto.DtoUpdateUser;
import com.ping.study.model.dto.registerUserDto;
+import com.ping.study.model.vo.user.CurrentUserVo;
+
import com.ping.study.pojo.NbaUser;
import com.ping.study.service.NbaUserService;
import com.ping.study.utils.MailUtil;
@@ -38,6 +41,9 @@ public class UserController {
@Autowired
private UserUtil userUtil;
+ @Autowired
+ private PayOrderMapper payOrderMapper;
+
//发送验证码
@RequestMapping("/send")
public String send(String email,Integer type) {
@@ -124,7 +130,7 @@ public class UserController {
// 1. session 已登录
NbaUser loginUser = (NbaUser) session.getAttribute("loginUser");
if (loginUser != null) {
- return loginUser.getEmail();
+ return buildCurrentUserVo(loginUser);
}
// 2. 尝试自动登录(记住我)
@@ -136,12 +142,17 @@ public class UserController {
session.setAttribute("loginUser", user);
log.info("自动登录成功 -> {}", user.getEmail());
- return user.getEmail();
+ return buildCurrentUserVo(user);
}
}
return "未登录";
}
+ private CurrentUserVo buildCurrentUserVo(NbaUser user) {
+ int rewardUser = payOrderMapper.countSuccessRewardByUserId(Long.valueOf(user.getId())) > 0 ? 1 : 0;
+ return new CurrentUserVo(user.getEmail(), rewardUser);
+ }
+
// 退出登录(清 session + 清 token + 清 cookie)
@PostMapping("/logout")
public String logout(
diff --git a/src/main/java/com/ping/study/mapper/NbaUserMapper.java b/src/main/java/com/ping/study/mapper/NbaUserMapper.java
index 953a8ae..3f56d2c 100644
--- a/src/main/java/com/ping/study/mapper/NbaUserMapper.java
+++ b/src/main/java/com/ping/study/mapper/NbaUserMapper.java
@@ -2,6 +2,8 @@ package com.ping.study.mapper;
import com.ping.study.pojo.NbaUser;
+import java.util.List;
+
/**
* @author Administrator
* @description 针对表【nba_user】的数据库操作Mapper
@@ -35,4 +37,6 @@ public interface NbaUserMapper {
NbaUser selectByEmail(String email);
void updatePasswordByEmail(String email, String password);
+
+ List selectAllUserEmail();
}
diff --git a/src/main/java/com/ping/study/mapper/PayOrderMapper.java b/src/main/java/com/ping/study/mapper/PayOrderMapper.java
new file mode 100644
index 0000000..01b557b
--- /dev/null
+++ b/src/main/java/com/ping/study/mapper/PayOrderMapper.java
@@ -0,0 +1,24 @@
+package com.ping.study.mapper;
+
+import com.ping.study.pojo.PayOrder;
+import org.apache.ibatis.annotations.Param;
+
+public interface PayOrderMapper {
+
+ int insert(PayOrder record);
+
+ PayOrder selectByOrderNo(@Param("orderNo") String orderNo);
+
+ int updateQrCodeAndStatus(@Param("orderNo") String orderNo,
+ @Param("qrCode") String qrCode,
+ @Param("status") String status);
+
+ int updatePaidByOrderNo(@Param("orderNo") String orderNo,
+ @Param("alipayTradeNo") String alipayTradeNo,
+ @Param("status") String status);
+
+ int updateStatusByOrderNo(@Param("orderNo") String orderNo,
+ @Param("status") String status);
+
+ int countSuccessRewardByUserId(@Param("userId") Long userId);
+}
diff --git a/src/main/java/com/ping/study/model/dto/pay/CreateRewardOrderDto.java b/src/main/java/com/ping/study/model/dto/pay/CreateRewardOrderDto.java
new file mode 100644
index 0000000..3d6c33c
--- /dev/null
+++ b/src/main/java/com/ping/study/model/dto/pay/CreateRewardOrderDto.java
@@ -0,0 +1,22 @@
+package com.ping.study.model.dto.pay;
+
+import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class CreateRewardOrderDto {
+
+ @NotNull(message = "打赏金额不能为空")
+ @DecimalMin(value = "0.01", message = "打赏金额不能小于0.01元")
+ private BigDecimal totalAmount;
+
+ @Size(max = 128, message = "打赏备注不能超过128个字符")
+ private String subject;
+
+ @Size(max = 500, message = "留言不能超过500个字符")
+ private String liuyan;
+}
diff --git a/src/main/java/com/ping/study/model/vo/pay/AlipayQrCodeVo.java b/src/main/java/com/ping/study/model/vo/pay/AlipayQrCodeVo.java
new file mode 100644
index 0000000..1804744
--- /dev/null
+++ b/src/main/java/com/ping/study/model/vo/pay/AlipayQrCodeVo.java
@@ -0,0 +1,21 @@
+package com.ping.study.model.vo.pay;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class AlipayQrCodeVo {
+
+ private String orderNo;
+
+ private String qrCode;
+
+ private BigDecimal totalAmount;
+
+ private String status;
+}
diff --git a/src/main/java/com/ping/study/model/vo/pay/PayOrderStatusVo.java b/src/main/java/com/ping/study/model/vo/pay/PayOrderStatusVo.java
new file mode 100644
index 0000000..37576a8
--- /dev/null
+++ b/src/main/java/com/ping/study/model/vo/pay/PayOrderStatusVo.java
@@ -0,0 +1,21 @@
+package com.ping.study.model.vo.pay;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class PayOrderStatusVo {
+
+ private String orderNo;
+
+ private String status;
+
+ private BigDecimal totalAmount;
+
+ private String alipayTradeNo;
+}
diff --git a/src/main/java/com/ping/study/model/vo/user/CurrentUserVo.java b/src/main/java/com/ping/study/model/vo/user/CurrentUserVo.java
new file mode 100644
index 0000000..331dca3
--- /dev/null
+++ b/src/main/java/com/ping/study/model/vo/user/CurrentUserVo.java
@@ -0,0 +1,18 @@
+package com.ping.study.model.vo.user;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class CurrentUserVo {
+
+ private String email;
+
+ /**
+ * 是否为赞赏用户:1 是,0 否
+ */
+ private Integer rewardUser;
+}
diff --git a/src/main/java/com/ping/study/pojo/PayOrder.java b/src/main/java/com/ping/study/pojo/PayOrder.java
new file mode 100644
index 0000000..07f8c61
--- /dev/null
+++ b/src/main/java/com/ping/study/pojo/PayOrder.java
@@ -0,0 +1,59 @@
+package com.ping.study.pojo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+public class PayOrder implements Serializable {
+
+ private Long id;
+
+ /**
+ * 商户订单号
+ */
+ private String orderNo;
+
+ /**
+ * 登录用户ID,匿名打赏时为空
+ */
+ private Long userId;
+
+ /**
+ * 订单标题/打赏备注
+ */
+ private String subject;
+
+ /**
+ * 打赏留言
+ */
+ private String liuyan;
+
+ /**
+ * 支付金额
+ */
+ private BigDecimal totalAmount;
+
+ /**
+ * WAIT_PAY / SUCCESS / CLOSED / FAILED
+ */
+ private String status;
+
+ /**
+ * 支付宝交易号
+ */
+ private String alipayTradeNo;
+
+ /**
+ * 支付宝返回的二维码链接
+ */
+ private String qrCode;
+
+ private Date createTime;
+
+ private Date updateTime;
+
+ private static final long serialVersionUID = 1L;
+}
diff --git a/src/main/java/com/ping/study/service/NbaUserService.java b/src/main/java/com/ping/study/service/NbaUserService.java
index 412cabf..939bb75 100644
--- a/src/main/java/com/ping/study/service/NbaUserService.java
+++ b/src/main/java/com/ping/study/service/NbaUserService.java
@@ -4,6 +4,8 @@ import com.ping.study.model.dto.DtoUpdateUser;
import com.ping.study.model.dto.registerUserDto;
import com.ping.study.pojo.NbaUser;
+import java.util.List;
+
/**
* @author Administrator
* @description 针对表【nba_user】的数据库操作Service
@@ -24,4 +26,6 @@ public interface NbaUserService{
String updateUser(DtoUpdateUser dtoUpdateUser);
NbaUser findByEmail(String email);
+
+ List selectAllUserEmail();
}
diff --git a/src/main/java/com/ping/study/service/impl/NbaUserServiceImpl.java b/src/main/java/com/ping/study/service/impl/NbaUserServiceImpl.java
index 0ccaf10..1705d96 100644
--- a/src/main/java/com/ping/study/service/impl/NbaUserServiceImpl.java
+++ b/src/main/java/com/ping/study/service/impl/NbaUserServiceImpl.java
@@ -8,15 +8,14 @@ import com.ping.study.pojo.LoginLog;
import com.ping.study.pojo.NbaUser;
import com.ping.study.service.NbaUserService;
-import com.ping.study.utils.UserUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
+import java.util.List;
@Service
@Slf4j
@@ -109,4 +108,10 @@ public class NbaUserServiceImpl implements NbaUserService {
return nbaUserMapper.findByEmail(email);
}
+ @Override
+ public List selectAllUserEmail() {
+ List emailList = nbaUserMapper.selectAllUserEmail();
+ return emailList;
+ }
+
}
diff --git a/src/main/java/com/ping/study/service/pay/AlipayService.java b/src/main/java/com/ping/study/service/pay/AlipayService.java
new file mode 100644
index 0000000..f0e49a5
--- /dev/null
+++ b/src/main/java/com/ping/study/service/pay/AlipayService.java
@@ -0,0 +1,17 @@
+package com.ping.study.service.pay;
+
+import com.ping.study.model.dto.pay.CreateRewardOrderDto;
+import com.ping.study.model.vo.pay.AlipayQrCodeVo;
+import com.ping.study.model.vo.pay.PayOrderStatusVo;
+import com.ping.study.pojo.NbaUser;
+
+import java.util.Map;
+
+public interface AlipayService {
+
+ AlipayQrCodeVo createRewardOrder(CreateRewardOrderDto dto, NbaUser loginUser) throws Exception;
+
+ String handleNotify(Map params) throws Exception;
+
+ PayOrderStatusVo queryOrderStatus(String orderNo);
+}
diff --git a/src/main/java/com/ping/study/service/pay/impl/AlipayServiceImpl.java b/src/main/java/com/ping/study/service/pay/impl/AlipayServiceImpl.java
new file mode 100644
index 0000000..04dc6c1
--- /dev/null
+++ b/src/main/java/com/ping/study/service/pay/impl/AlipayServiceImpl.java
@@ -0,0 +1,170 @@
+package com.ping.study.service.pay.impl;
+
+import com.alipay.api.AlipayClient;
+import com.alipay.api.DefaultAlipayClient;
+import com.alipay.api.internal.util.AlipaySignature;
+import com.alipay.api.request.AlipayTradePrecreateRequest;
+import com.alipay.api.response.AlipayTradePrecreateResponse;
+import com.alipay.api.domain.AlipayTradePrecreateModel;
+import com.ping.study.config.AlipayConfig;
+import com.ping.study.mapper.PayOrderMapper;
+import com.ping.study.model.dto.pay.CreateRewardOrderDto;
+import com.ping.study.model.vo.pay.AlipayQrCodeVo;
+import com.ping.study.model.vo.pay.PayOrderStatusVo;
+import com.ping.study.pojo.NbaUser;
+import com.ping.study.pojo.PayOrder;
+import com.ping.study.service.pay.AlipayService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+
+@Service
+@Slf4j
+public class AlipayServiceImpl implements AlipayService {
+
+ private static final String STATUS_WAIT_PAY = "WAIT_PAY";
+ private static final String STATUS_SUCCESS = "SUCCESS";
+ private static final String STATUS_FAILED = "FAILED";
+
+ private final AlipayConfig alipayConfig;
+ private final PayOrderMapper payOrderMapper;
+
+ public AlipayServiceImpl(AlipayConfig alipayConfig, PayOrderMapper payOrderMapper) {
+ this.alipayConfig = alipayConfig;
+ this.payOrderMapper = payOrderMapper;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public AlipayQrCodeVo createRewardOrder(CreateRewardOrderDto dto, NbaUser loginUser) throws Exception {
+ BigDecimal totalAmount = dto.getTotalAmount().setScale(2, RoundingMode.HALF_UP);
+ String subject = StringUtils.hasText(dto.getSubject()) ? dto.getSubject() : "NBA赛事平台打赏";
+ String liuyan = StringUtils.hasText(dto.getLiuyan()) ? dto.getLiuyan() : null;
+ String orderNo = generateOrderNo();
+
+ PayOrder order = new PayOrder();
+ order.setOrderNo(orderNo);
+ order.setUserId(loginUser == null ? null : Long.valueOf(loginUser.getId()));
+ order.setSubject(subject);
+ order.setLiuyan(liuyan);
+ order.setTotalAmount(totalAmount);
+ order.setStatus(STATUS_WAIT_PAY);
+ order.setCreateTime(new Date());
+ order.setUpdateTime(new Date());
+ payOrderMapper.insert(order);
+
+ AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
+ request.setNotifyUrl(alipayConfig.getNotifyUrl());
+
+ AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
+ model.setOutTradeNo(orderNo);
+ model.setTotalAmount(totalAmount.toPlainString());
+ model.setSubject(subject);
+ model.setTimeoutExpress("30m");
+ request.setBizModel(model);
+
+ AlipayTradePrecreateResponse response = buildAlipayClient().execute(request);
+ if (response.isSuccess()) {
+ String qrCode = response.getQrCode();
+ payOrderMapper.updateQrCodeAndStatus(orderNo, qrCode, STATUS_WAIT_PAY);
+ return new AlipayQrCodeVo(orderNo, qrCode, totalAmount, STATUS_WAIT_PAY);
+ }
+
+ payOrderMapper.updateStatusByOrderNo(orderNo, STATUS_FAILED);
+ log.error("支付宝预下单失败,orderNo={}, code={}, subCode={}, msg={}, subMsg={}",
+ orderNo, response.getCode(), response.getSubCode(), response.getMsg(), response.getSubMsg());
+ throw new RuntimeException("支付宝预下单失败:" + response.getSubMsg());
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public String handleNotify(Map params) throws Exception {
+ boolean signVerified = AlipaySignature.rsaCheckV1(
+ params,
+ alipayConfig.getAlipayPublicKey(),
+ alipayConfig.getCharset(),
+ alipayConfig.getSignType()
+ );
+
+ if (!signVerified) {
+ log.warn("支付宝异步通知验签失败:{}", params);
+ return "failure";
+ }
+
+ String tradeStatus = params.get("trade_status");
+ String outTradeNo = params.get("out_trade_no");
+ String tradeNo = params.get("trade_no");
+ String totalAmount = params.get("total_amount");
+ String appId = params.get("app_id");
+
+ if (!Objects.equals(appId, alipayConfig.getAppId())) {
+ log.warn("支付宝异步通知app_id不匹配,orderNo={}, notifyAppId={}", outTradeNo, appId);
+ return "failure";
+ }
+
+ PayOrder order = payOrderMapper.selectByOrderNo(outTradeNo);
+ if (order == null) {
+ log.warn("支付宝异步通知订单不存在,orderNo={}", outTradeNo);
+ return "failure";
+ }
+
+ if (!amountEquals(order.getTotalAmount(), new BigDecimal(totalAmount))) {
+ log.warn("支付宝异步通知金额不匹配,orderNo={}, localAmount={}, notifyAmount={}",
+ outTradeNo, order.getTotalAmount(), totalAmount);
+ return "failure";
+ }
+
+ if (STATUS_SUCCESS.equals(order.getStatus())) {
+ return "success";
+ }
+
+ if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {
+ payOrderMapper.updatePaidByOrderNo(outTradeNo, tradeNo, STATUS_SUCCESS);
+ log.info("支付宝打赏支付成功,orderNo={}, tradeNo={}, amount={}", outTradeNo, tradeNo, totalAmount);
+ }
+
+ return "success";
+ }
+
+ @Override
+ public PayOrderStatusVo queryOrderStatus(String orderNo) {
+ PayOrder order = payOrderMapper.selectByOrderNo(orderNo);
+ if (order == null) {
+ throw new RuntimeException("订单不存在");
+ }
+ return new PayOrderStatusVo(order.getOrderNo(), order.getStatus(), order.getTotalAmount(), order.getAlipayTradeNo());
+ }
+
+ private AlipayClient buildAlipayClient() {
+ return new DefaultAlipayClient(
+ alipayConfig.getGatewayUrl(),
+ alipayConfig.getAppId(),
+ alipayConfig.getPrivateKey(),
+ alipayConfig.getFormat(),
+ alipayConfig.getCharset(),
+ alipayConfig.getAlipayPublicKey(),
+ alipayConfig.getSignType()
+ );
+ }
+
+ private String generateOrderNo() {
+ String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
+ String random = UUID.randomUUID().toString().replace("-", "").substring(0, 8).toUpperCase();
+ return "REWARD" + time + random;
+ }
+
+ private boolean amountEquals(BigDecimal localAmount, BigDecimal notifyAmount) {
+ return localAmount.setScale(2, RoundingMode.HALF_UP)
+ .compareTo(notifyAmount.setScale(2, RoundingMode.HALF_UP)) == 0;
+ }
+}
diff --git a/src/main/java/com/ping/study/utils/MailUtil.java b/src/main/java/com/ping/study/utils/MailUtil.java
index 62bbcb3..9daa4ea 100644
--- a/src/main/java/com/ping/study/utils/MailUtil.java
+++ b/src/main/java/com/ping/study/utils/MailUtil.java
@@ -4,10 +4,11 @@ import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.mail.MailAuthenticationException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
-import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@@ -27,10 +28,12 @@ public class MailUtil {
private RedisUtil redisUtil;
private final JavaMailSender javaMailSender;
+ private final String fromAddress;
@Autowired
- public MailUtil(JavaMailSender javaMailSender) {
+ public MailUtil(JavaMailSender javaMailSender, @Value("${spring.mail.username}") String fromAddress) {
this.javaMailSender = javaMailSender;
+ this.fromAddress = fromAddress;
}
/**
@@ -56,7 +59,7 @@ public class MailUtil {
String verificationCode = generateVerificationCode();
// 构造邮件
SimpleMailMessage message = new SimpleMailMessage();
- message.setFrom("xdd9@vip.qq.com");
+ message.setFrom(fromAddress);
message.setTo(to);
message.setSubject("【NBA轻松看验证码】");
message.setText("您的验证码是:" + verificationCode + ",有效期 5 分钟"+"。请勿回复此邮件。"+"\n反馈邮箱:super@2026123.xyz"+"\n发布页:2026123.xyz");
@@ -87,25 +90,74 @@ public class MailUtil {
* @param text 邮件内容
*/
public String sendEmail2(String to,String subject,String text) {
- Assert.hasText(to, "邮箱不能为空");
+ SendMailResult result = sendEmailResult(to, subject, text);
+ return result.isSuccess() ? "发送成功" : "发送邮件失败";
+ }
+
+ public SendMailResult sendEmailResult(String to, String subject, String text) {
+ Assert.hasText(to, "邮箱不能为空");
+ Assert.hasText(subject, "邮件主题不能为空");
+ Assert.hasText(text, "邮件内容不能为空");
- // 构造邮件
SimpleMailMessage message = new SimpleMailMessage();
- message.setFrom("xdd9@vip.qq.com");
+ message.setFrom(fromAddress);
message.setTo(to);
message.setSubject(subject);
message.setText(text);
-// message.setText("发布页:2026123.xyz");
- // 发送邮件
+
try {
- javaMailSender.send(message);
- return "发送成功";
-// return verificationCode;
- }catch (Exception e){
- log.info("发送邮件失败:{}", e.getMessage());
- return "发送邮件失败";
+ javaMailSender.send(message);
+ return SendMailResult.success();
+ } catch (Exception e) {
+ boolean authenticationFailure = isAuthenticationFailure(e);
+ log.info("发送邮件失败:{},原因:{}", to, e.getMessage(), e);
+ return SendMailResult.failure(authenticationFailure, e.getMessage());
}
}
+
+ private boolean isAuthenticationFailure(Throwable throwable) {
+ while (throwable != null) {
+ if (throwable instanceof MailAuthenticationException
+ || throwable.getClass().getName().contains("AuthenticationFailedException")) {
+ return true;
+ }
+ throwable = throwable.getCause();
+ }
+ return false;
+ }
+
+ public static class SendMailResult {
+ private final boolean success;
+ private final boolean authenticationFailure;
+ private final String message;
+
+ private SendMailResult(boolean success, boolean authenticationFailure, String message) {
+ this.success = success;
+ this.authenticationFailure = authenticationFailure;
+ this.message = message;
+ }
+
+ public static SendMailResult success() {
+ return new SendMailResult(true, false, "发送成功");
+ }
+
+ public static SendMailResult failure(boolean authenticationFailure, String message) {
+ return new SendMailResult(false, authenticationFailure, message);
+ }
+
+ public boolean isSuccess() {
+ return success;
+ }
+
+ public boolean isAuthenticationFailure() {
+ return authenticationFailure;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+ }
+
/**
*
* @param map
@@ -115,7 +167,7 @@ public class MailUtil {
// 构造邮件
SimpleMailMessage message = new SimpleMailMessage();
- message.setFrom("xdd9@vip.qq.com");
+ message.setFrom(fromAddress);
message.setTo(map.get("mail"));
message.setSubject(map.get("subject"));
message.setText(map.get("text"));
@@ -130,13 +182,14 @@ public class MailUtil {
* @param subject 邮件主题
* @param html HTML 内容
*/
- public void sendHtmlEmail(String to, String subject, String html) throws MessagingException, jakarta.mail.MessagingException {
+ public void sendHtmlEmail(String to, String subject, String html) throws MessagingException, MessagingException {
Assert.hasText(to, "邮箱不能为空");
Assert.hasText(subject, "邮件主题不能为空");
Assert.hasText(html, "HTML 内容不能为空");
- jakarta.mail.internet.MimeMessage message = javaMailSender.createMimeMessage();
+ MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
+ helper.setFrom(fromAddress);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(html, true); // true 表示支持 HTML
@@ -151,14 +204,15 @@ public class MailUtil {
* @param text 邮件内容
* @param attachments 附件文件列表
*/
- public void sendEmailWithAttachments(String to, String subject, String text, List attachments) throws MessagingException, jakarta.mail.MessagingException {
+ public void sendEmailWithAttachments(String to, String subject, String text, List attachments) throws MessagingException, MessagingException {
Assert.hasText(to, "收件人邮箱不能为空");
Assert.hasText(subject, "邮件主题不能为空");
Assert.hasText(text, "邮件内容不能为空");
Assert.notEmpty(attachments, "附件列表不能为空");
- jakarta.mail.internet.MimeMessage message = javaMailSender.createMimeMessage();
+ MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
+ helper.setFrom(fromAddress);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(text);
@@ -179,7 +233,7 @@ public class MailUtil {
* @param html HTML 内容
* @param inlineFiles 内联资源文件列表
*/
- public void sendHtmlEmailWithInline(String to, String subject, String html, List inlineFiles) throws MessagingException, jakarta.mail.MessagingException {
+ public void sendHtmlEmailWithInline(String to, String subject, String html, List inlineFiles) throws MessagingException, MessagingException {
Assert.hasText(to, "收件人邮箱不能为空");
Assert.hasText(subject, "邮件主题不能为空");
Assert.hasText(html, "HTML 内容不能为空");
@@ -187,6 +241,7 @@ public class MailUtil {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
+ helper.setFrom(fromAddress);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(html, true);
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 65b75a6..b1f79b5 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -4,7 +4,7 @@ server:
cookie:
same-site: None
secure: true # 生产是 https 必须 true
- port: 9001
+ port: 9005
#spring:
# data:
# redis:
@@ -29,8 +29,8 @@ spring:
name: NBA
datasource:
#mysql5.7.4配置
- driver-class-name: com.mysql.jdbc.Driver
- url: jdbc:mysql://localhost:3306/nba?useUnicode=true&characterEncoding=utf-8&useSSL=false
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/nba?useSSL=true&trustServerCertificate=true&serverTimezone=UTC
# url: jdbc:mysql://116.62.173.2:3306/nba?useUnicode=true&characterEncoding=utf-8&useSSL=false
# url: jdbc:mysql://154.36.154.211:9002/nba?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
@@ -43,24 +43,32 @@ spring:
mail:
host: smtp.qq.com
port: 465
- username: xdd9@vip.qq.com
- password: qhcladjicfydbejj
+ username: ${MAIL_USERNAME:xdd9@vip.qq.com}
+ password: ${MAIL_PASSWORD:qhcladjicfydbejj}
default-encoding: UTF-8
properties:
+ mail.smtp.auth: true
+ mail.smtp.ssl.enable: true
+ mail.smtp.ssl.trust: smtp.qq.com
+ mail.smtp.starttls.enable: false
+ mail.smtp.starttls.required: false
mail.smtp.connectiontimeout: 5000 # 连接超时 5 秒
mail.smtp.timeout: 5000 # 读超时 5 秒
mail.smtp.writetimeout: 5000 # 写超时 5 秒
- mail:
- smtp:
- auth: true
- ssl:
- enable: true
- starttls:
- enable: true
- required: true
mybatis:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
type-aliases-package: com.ping.study.pojo
- mapper-locations: classpath:mapper/*.xml
\ No newline at end of file
+ mapper-locations: classpath:mapper/*.xml
+alipay:
+ # 沙箱网关:https://openapi-sandbox.dl.alipaydev.com/gateway.do
+ # 正式网关:https://openapi.alipay.com/gateway.do
+ gateway-url: ${ALIPAY_GATEWAY_URL:https://openapi.alipay.com/gateway.do}
+ app-id: ${ALIPAY_APP_ID:2021002176680273}
+ private-key: ${ALIPAY_PRIVATE_KEY:MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDuAjvOJA0WdAkKIJu/TVUqjtA7eSB1HjYpfPWSRB1wE7N+J5Wf8aw9ieI9UIlaO5KzhL6+fRy6hKtZoFtVZarKhSmbZhL3ccgP3ulK5FZwe6UgqQi13J9Akr8sRy/XTBrRmPyntjktKTO8830K5s5it1KT5einVWf4T976WQnyz0/MQ2dMT0Cpatl+d8LYZeeGrW7tlpKY+dEBORLF+nI2OXCakeprnAPcDjqEccdLcA8rwVyduDJrrJwhp/tKmOPjzG/B7GOciv9M/N6IXHmQJbqQDy6Qgku+ALuvu9HJsdWZuetv8K9nX7ofJhdApKZLYBiap7GrMrDK5QwtMANXAgMBAAECggEATzp/HgfyM189AxoHvo7HovsXZjTUESiVbwoh1SbHhd+JCJ20uSGB7JpRrYd5sYfTNd8if9a2EYDIaXVv8eV+DRx21TwNZEGED4RstHl+LMN8HHsoYyGmAaDX8q1fx5OV+d+re0i1j8r/Zg7HuTFL0qCj5IfMAqfzjD1KRJwPK7wit6nNTn9t6RPQRo0QHSazkGjoHKAcYkKTxor9D6HKVU/fQnoI6Hu6bnsTC7VXSY28am+Is79z0cvMc4n3PpBQBdA2VRKowc0frBkusDLhdMvWrvCOgRsKtgrbvZRT3qo+YJdPC5QKokslVlY3nC51dCWxAfR9YQyBEXWIGOBOgQKBgQD9MoQroPOSRN5TwfDLRDZYEL+tzeVTkDXKum40vca3WL9mT5Xuq1NVv9kHQzfmpmTEphsqZX0qjN9Vteic5J644jHVq1gP/xC75U81Lk74KwI/2/OU4YjxZrZU+zksGLUZxrk6ZkvHu4uvamf0hMTv0ThHXWaNc26fcYIPe5RQfwKBgQDwpK1uo/rv6Y0R3ZpSPLULMyFhTiiNBHWpO3Ks53PrCLpKqhDIvzJltLEZQ/Gm1HyHihvhQo4dupH3SoizQY76ZoSWFVjttRC6tQWvP+/SOeS6scUTm+YoStVpJAjHc51L5AV7xqyfsBQp2wCfv6bOAm05ojNZeXT/0Xh3aJFhKQKBgCbkop7dC201fP1atjVTFhHzCK6XCRoL9eddOd6KJEM3s7bwlLRjxtpOLPVLowOgHayDY3rguhhWKVxOJBwtliAKRuNufYl/gb+LGt6tbV37pU3P6Ju/BT0nrOxi5sZaST9bYkqROiTFL1DTxIIv/txjxARaWY1y5NRDKFpefkrbAoGAZ4VgOoV+cPDWyAW92nzVIVFIndSCq09s0nWJeopDGqvxgCcy+zkNFCWsPgM1lKA6RhCKt9fqzS0yl+BPeFXOjTfG982NKQ1IehlfralioNxY4luRPUNGurSepBFlWXAKDdi05y9rmXKk6fCVjyNiPaNbm3yEfIw8YcSZ/zxTtikCgYBMQAUvZrsgeHj0RXIBY2HPHOjjB/lpAXwvjkjeHInjnDUP2jbY2SJxmU2kn37QnjayvTCdMtDDdEcAajyXMJ7RC8uICIRXLVckx1JJvyEnogsaucPVAm4CbXRDaFNc8R+HoO+LbWE3c6gHY3mL8nNYUTdzkOULfCVvUy2vr/lKMg==}
+ alipay-public-key: ${ALIPAY_PUBLIC_KEY:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAh8inNmeH0tPN5U4AW1+o7EJjaDqLs+gk+4ZnBX/ZkgOldB7NAeXCPzzANNyNYih7cR/M0DnwoH9URm4m4b3fKRlYq18/SrxG0jF6wISMhgc3EMQRBzQ3mkn5Szs/YEwrg/UjcAleLbKl89eDRneXrHv6R6ArFgkwPNtsVCac3P0yEu6F+pCLDsKCDQQIOWUbnjKvOFUebh53nXVnUIGSiWC9fYS7KpdM2JetjnD+u9lH4b/7RlM/R06+K3cPZx6LmANksFfH8gO/dtwaklavNq1KnSVCjgnejocj8zEi2PRmOEZpTHqM+T/0dMCN4zvwji/xoQ8sLV3ywm52o9Ew7wIDAQAB}
+ notify-url: ${ALIPAY_NOTIFY_URL:https://api9.jrs77.xyz/pay/alipay/notify}
+ charset: UTF-8
+ format: JSON
+ sign-type: RSA2
diff --git a/src/main/resources/com/ping/study/mapper/NbaUserMapper.xml b/src/main/resources/com/ping/study/mapper/NbaUserMapper.xml
index 0e3b7b1..ccec2ad 100644
--- a/src/main/resources/com/ping/study/mapper/NbaUserMapper.xml
+++ b/src/main/resources/com/ping/study/mapper/NbaUserMapper.xml
@@ -136,4 +136,10 @@
update nba_user set password = #{password} where email = #{email}
+
+
diff --git a/src/main/resources/mapper/PayOrderMapper.xml b/src/main/resources/mapper/PayOrderMapper.xml
new file mode 100644
index 0000000..4ca03ba
--- /dev/null
+++ b/src/main/resources/mapper/PayOrderMapper.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id, order_no, user_id, subject, liuyan, total_amount, status, alipay_trade_no, qr_code, create_time, update_time
+
+
+
+ insert into pay_order
+ (order_no, user_id, subject, liuyan, total_amount, status, alipay_trade_no, qr_code, create_time, update_time)
+ values
+ (#{orderNo}, #{userId}, #{subject}, #{liuyan}, #{totalAmount}, #{status}, #{alipayTradeNo}, #{qrCode}, #{createTime}, #{updateTime})
+
+
+
+
+
+ update pay_order
+ set qr_code = #{qrCode},
+ status = #{status},
+ update_time = now()
+ where order_no = #{orderNo}
+
+
+
+ update pay_order
+ set alipay_trade_no = #{alipayTradeNo},
+ status = #{status},
+ update_time = now()
+ where order_no = #{orderNo}
+ and status != 'SUCCESS'
+
+
+
+ update pay_order
+ set status = #{status},
+ update_time = now()
+ where order_no = #{orderNo}
+
+
+