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} + + +