增加支付宝打赏

This commit is contained in:
2026-05-30 23:09:25 +08:00
parent a7e57e63a9
commit 7838f84e81
21 changed files with 663 additions and 41 deletions

View File

@@ -18,6 +18,7 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>17</java.version>
</properties> </properties>
<dependencies> <dependencies>
@@ -62,6 +63,12 @@
<!-- <artifactId>gson</artifactId>--> <!-- <artifactId>gson</artifactId>-->
<!-- <version>2.10.1</version>--> <!-- <version>2.10.1</version>-->
<!-- </dependency>--> <!-- </dependency>-->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.40.806.ALL</version>
</dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>

View File

@@ -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;
}

View File

@@ -17,7 +17,7 @@ public class WebMvc implements WebMvcConfigurer {
.allowedOrigins("http://jrs77.xyz","https://jrs77.xyz", .allowedOrigins("http://jrs77.xyz","https://jrs77.xyz",
"http://localhost:5173", "https://nba.1024x.icu", "http://localhost:5173", "https://nba.1024x.icu",
"http://nba.1024x.icu","http://167.253.156.235:9006", "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") .allowedMethods("GET","POST","PUT","DELETE","OPTIONS")
.allowedHeaders("X-Timestamp","X-Sign","*") .allowedHeaders("X-Timestamp","X-Sign","*")
.allowCredentials(true) // 若不用 Cookie 也可为 false .allowCredentials(true) // 若不用 Cookie 也可为 false

View File

@@ -143,10 +143,10 @@ public class NbaController {
if (loginUser == null){ if (loginUser == null){
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("请先登录"); return ResponseEntity.status(HttpStatus.FORBIDDEN).body("请先登录");
} }
log.info("{}:获取直播链接开始",loginUser.getEmail()); // log.info("{}:获取直播链接开始",loginUser.getEmail());
String ip = userUtil.getClientIp(request); String ip = userUtil.getClientIp(request);
String url = urlsService.findUrlByGameIdAndType(gameId, playType); String url = urlsService.findUrlByGameIdAndType(gameId, playType);
// log.info("来自ip:{} 用户:{}:获取直播链接成功:{}",ip,loginUser.getEmail(),url); log.info("来自ip:{} 用户:{}:获取直播链接成功:{}",ip,loginUser.getEmail(),url);
return ResponseEntity.ok(url); return ResponseEntity.ok(url);
} }

View File

@@ -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<String, String> params = new HashMap<>();
Map<String, String[]> requestParams = request.getParameterMap();
for (Map.Entry<String, String[]> 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();
}
}

View File

@@ -1,9 +1,12 @@
package com.ping.study.controller.user; 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.DtoUpdateUser;
import com.ping.study.model.dto.registerUserDto; 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.pojo.NbaUser;
import com.ping.study.service.NbaUserService; import com.ping.study.service.NbaUserService;
import com.ping.study.utils.MailUtil; import com.ping.study.utils.MailUtil;
@@ -38,6 +41,9 @@ public class UserController {
@Autowired @Autowired
private UserUtil userUtil; private UserUtil userUtil;
@Autowired
private PayOrderMapper payOrderMapper;
//发送验证码 //发送验证码
@RequestMapping("/send") @RequestMapping("/send")
public String send(String email,Integer type) { public String send(String email,Integer type) {
@@ -124,7 +130,7 @@ public class UserController {
// 1. session 已登录 // 1. session 已登录
NbaUser loginUser = (NbaUser) session.getAttribute("loginUser"); NbaUser loginUser = (NbaUser) session.getAttribute("loginUser");
if (loginUser != null) { if (loginUser != null) {
return loginUser.getEmail(); return buildCurrentUserVo(loginUser);
} }
// 2. 尝试自动登录(记住我) // 2. 尝试自动登录(记住我)
@@ -136,12 +142,17 @@ public class UserController {
session.setAttribute("loginUser", user); session.setAttribute("loginUser", user);
log.info("自动登录成功 -> {}", user.getEmail()); log.info("自动登录成功 -> {}", user.getEmail());
return user.getEmail(); return buildCurrentUserVo(user);
} }
} }
return "未登录"; 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 // 退出登录(清 session + 清 token + 清 cookie
@PostMapping("/logout") @PostMapping("/logout")
public String logout( public String logout(

View File

@@ -2,6 +2,8 @@ package com.ping.study.mapper;
import com.ping.study.pojo.NbaUser; import com.ping.study.pojo.NbaUser;
import java.util.List;
/** /**
* @author Administrator * @author Administrator
* @description 针对表【nba_user】的数据库操作Mapper * @description 针对表【nba_user】的数据库操作Mapper
@@ -35,4 +37,6 @@ public interface NbaUserMapper {
NbaUser selectByEmail(String email); NbaUser selectByEmail(String email);
void updatePasswordByEmail(String email, String password); void updatePasswordByEmail(String email, String password);
List<String> selectAllUserEmail();
} }

View File

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

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -4,6 +4,8 @@ import com.ping.study.model.dto.DtoUpdateUser;
import com.ping.study.model.dto.registerUserDto; import com.ping.study.model.dto.registerUserDto;
import com.ping.study.pojo.NbaUser; import com.ping.study.pojo.NbaUser;
import java.util.List;
/** /**
* @author Administrator * @author Administrator
* @description 针对表【nba_user】的数据库操作Service * @description 针对表【nba_user】的数据库操作Service
@@ -24,4 +26,6 @@ public interface NbaUserService{
String updateUser(DtoUpdateUser dtoUpdateUser); String updateUser(DtoUpdateUser dtoUpdateUser);
NbaUser findByEmail(String email); NbaUser findByEmail(String email);
List<String> selectAllUserEmail();
} }

View File

@@ -8,15 +8,14 @@ import com.ping.study.pojo.LoginLog;
import com.ping.study.pojo.NbaUser; import com.ping.study.pojo.NbaUser;
import com.ping.study.service.NbaUserService; import com.ping.study.service.NbaUserService;
import com.ping.study.utils.UserUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.Date; import java.util.Date;
import java.util.List;
@Service @Service
@Slf4j @Slf4j
@@ -109,4 +108,10 @@ public class NbaUserServiceImpl implements NbaUserService {
return nbaUserMapper.findByEmail(email); return nbaUserMapper.findByEmail(email);
} }
@Override
public List<String> selectAllUserEmail() {
List<String> emailList = nbaUserMapper.selectAllUserEmail();
return emailList;
}
} }

View File

@@ -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<String, String> params) throws Exception;
PayOrderStatusVo queryOrderStatus(String orderNo);
}

View File

@@ -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<String, String> 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;
}
}

View File

@@ -4,10 +4,11 @@ import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage; import jakarta.mail.internet.MimeMessage;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; 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.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@@ -27,10 +28,12 @@ public class MailUtil {
private RedisUtil redisUtil; private RedisUtil redisUtil;
private final JavaMailSender javaMailSender; private final JavaMailSender javaMailSender;
private final String fromAddress;
@Autowired @Autowired
public MailUtil(JavaMailSender javaMailSender) { public MailUtil(JavaMailSender javaMailSender, @Value("${spring.mail.username}") String fromAddress) {
this.javaMailSender = javaMailSender; this.javaMailSender = javaMailSender;
this.fromAddress = fromAddress;
} }
/** /**
@@ -56,7 +59,7 @@ public class MailUtil {
String verificationCode = generateVerificationCode(); String verificationCode = generateVerificationCode();
// 构造邮件 // 构造邮件
SimpleMailMessage message = new SimpleMailMessage(); SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("xdd9@vip.qq.com"); message.setFrom(fromAddress);
message.setTo(to); message.setTo(to);
message.setSubject("【NBA轻松看验证码】"); message.setSubject("【NBA轻松看验证码】");
message.setText("您的验证码是:" + verificationCode + ",有效期 5 分钟"+"。请勿回复此邮件。"+"\n反馈邮箱super@2026123.xyz"+"\n发布页2026123.xyz"); message.setText("您的验证码是:" + verificationCode + ",有效期 5 分钟"+"。请勿回复此邮件。"+"\n反馈邮箱super@2026123.xyz"+"\n发布页2026123.xyz");
@@ -87,25 +90,74 @@ public class MailUtil {
* @param text 邮件内容 * @param text 邮件内容
*/ */
public String sendEmail2(String to,String subject,String 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(); SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("xdd9@vip.qq.com"); message.setFrom(fromAddress);
message.setTo(to); message.setTo(to);
message.setSubject(subject); message.setSubject(subject);
message.setText(text); message.setText(text);
// message.setText("发布页2026123.xyz");
// 发送邮件
try { try {
javaMailSender.send(message); javaMailSender.send(message);
return "发送成功"; return SendMailResult.success();
// return verificationCode;
} catch (Exception e) { } catch (Exception e) {
log.info("发送邮件失败:{}", e.getMessage()); boolean authenticationFailure = isAuthenticationFailure(e);
return "发送邮件失败"; 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 * @param map
@@ -115,7 +167,7 @@ public class MailUtil {
// 构造邮件 // 构造邮件
SimpleMailMessage message = new SimpleMailMessage(); SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("xdd9@vip.qq.com"); message.setFrom(fromAddress);
message.setTo(map.get("mail")); message.setTo(map.get("mail"));
message.setSubject(map.get("subject")); message.setSubject(map.get("subject"));
message.setText(map.get("text")); message.setText(map.get("text"));
@@ -130,13 +182,14 @@ public class MailUtil {
* @param subject 邮件主题 * @param subject 邮件主题
* @param html HTML 内容 * @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(to, "邮箱不能为空");
Assert.hasText(subject, "邮件主题不能为空"); Assert.hasText(subject, "邮件主题不能为空");
Assert.hasText(html, "HTML 内容不能为空"); Assert.hasText(html, "HTML 内容不能为空");
jakarta.mail.internet.MimeMessage message = javaMailSender.createMimeMessage(); MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true); MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(fromAddress);
helper.setTo(to); helper.setTo(to);
helper.setSubject(subject); helper.setSubject(subject);
helper.setText(html, true); // true 表示支持 HTML helper.setText(html, true); // true 表示支持 HTML
@@ -151,14 +204,15 @@ public class MailUtil {
* @param text 邮件内容 * @param text 邮件内容
* @param attachments 附件文件列表 * @param attachments 附件文件列表
*/ */
public void sendEmailWithAttachments(String to, String subject, String text, List<File> attachments) throws MessagingException, jakarta.mail.MessagingException { public void sendEmailWithAttachments(String to, String subject, String text, List<File> attachments) throws MessagingException, MessagingException {
Assert.hasText(to, "收件人邮箱不能为空"); Assert.hasText(to, "收件人邮箱不能为空");
Assert.hasText(subject, "邮件主题不能为空"); Assert.hasText(subject, "邮件主题不能为空");
Assert.hasText(text, "邮件内容不能为空"); Assert.hasText(text, "邮件内容不能为空");
Assert.notEmpty(attachments, "附件列表不能为空"); Assert.notEmpty(attachments, "附件列表不能为空");
jakarta.mail.internet.MimeMessage message = javaMailSender.createMimeMessage(); MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true); MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(fromAddress);
helper.setTo(to); helper.setTo(to);
helper.setSubject(subject); helper.setSubject(subject);
helper.setText(text); helper.setText(text);
@@ -179,7 +233,7 @@ public class MailUtil {
* @param html HTML 内容 * @param html HTML 内容
* @param inlineFiles 内联资源文件列表 * @param inlineFiles 内联资源文件列表
*/ */
public void sendHtmlEmailWithInline(String to, String subject, String html, List<File> inlineFiles) throws MessagingException, jakarta.mail.MessagingException { public void sendHtmlEmailWithInline(String to, String subject, String html, List<File> inlineFiles) throws MessagingException, MessagingException {
Assert.hasText(to, "收件人邮箱不能为空"); Assert.hasText(to, "收件人邮箱不能为空");
Assert.hasText(subject, "邮件主题不能为空"); Assert.hasText(subject, "邮件主题不能为空");
Assert.hasText(html, "HTML 内容不能为空"); Assert.hasText(html, "HTML 内容不能为空");
@@ -187,6 +241,7 @@ public class MailUtil {
MimeMessage message = javaMailSender.createMimeMessage(); MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true); MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(fromAddress);
helper.setTo(to); helper.setTo(to);
helper.setSubject(subject); helper.setSubject(subject);
helper.setText(html, true); helper.setText(html, true);

View File

@@ -4,7 +4,7 @@ server:
cookie: cookie:
same-site: None same-site: None
secure: true # 生产是 https 必须 true secure: true # 生产是 https 必须 true
port: 9001 port: 9005
#spring: #spring:
# data: # data:
# redis: # redis:
@@ -29,8 +29,8 @@ spring:
name: NBA name: NBA
datasource: datasource:
#mysql5.7.4配置 #mysql5.7.4配置
driver-class-name: com.mysql.jdbc.Driver driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/nba?useUnicode=true&characterEncoding=utf-8&useSSL=false 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://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 # url: jdbc:mysql://154.36.154.211:9002/nba?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root username: root
@@ -43,24 +43,32 @@ spring:
mail: mail:
host: smtp.qq.com host: smtp.qq.com
port: 465 port: 465
username: xdd9@vip.qq.com username: ${MAIL_USERNAME:xdd9@vip.qq.com}
password: qhcladjicfydbejj password: ${MAIL_PASSWORD:qhcladjicfydbejj}
default-encoding: UTF-8 default-encoding: UTF-8
properties: 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.connectiontimeout: 5000 # 连接超时 5 秒
mail.smtp.timeout: 5000 # 读超时 5 秒 mail.smtp.timeout: 5000 # 读超时 5 秒
mail.smtp.writetimeout: 5000 # 写超时 5 秒 mail.smtp.writetimeout: 5000 # 写超时 5 秒
mail:
smtp:
auth: true
ssl:
enable: true
starttls:
enable: true
required: true
mybatis: mybatis:
configuration: configuration:
map-underscore-to-camel-case: true map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
type-aliases-package: com.ping.study.pojo type-aliases-package: com.ping.study.pojo
mapper-locations: classpath:mapper/*.xml 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

View File

@@ -136,4 +136,10 @@
update nba_user set password = #{password} where email = #{email} update nba_user set password = #{password} where email = #{email}
</update> </update>
<select id="selectAllUserEmail">
select email from nba_user where email is not null
-- select email from nba_user where email like "113130%"
</select>
</mapper> </mapper>

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ping.study.mapper.PayOrderMapper">
<resultMap id="BaseResultMap" type="com.ping.study.pojo.PayOrder">
<id property="id" column="id"/>
<result property="orderNo" column="order_no"/>
<result property="userId" column="user_id"/>
<result property="subject" column="subject"/>
<result property="liuyan" column="liuyan"/>
<result property="totalAmount" column="total_amount"/>
<result property="status" column="status"/>
<result property="alipayTradeNo" column="alipay_trade_no"/>
<result property="qrCode" column="qr_code"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<sql id="Base_Column_List">
id, order_no, user_id, subject, liuyan, total_amount, status, alipay_trade_no, qr_code, create_time, update_time
</sql>
<insert id="insert" parameterType="com.ping.study.pojo.PayOrder" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
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})
</insert>
<select id="selectByOrderNo" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from pay_order
where order_no = #{orderNo}
limit 1
</select>
<update id="updateQrCodeAndStatus">
update pay_order
set qr_code = #{qrCode},
status = #{status},
update_time = now()
where order_no = #{orderNo}
</update>
<update id="updatePaidByOrderNo">
update pay_order
set alipay_trade_no = #{alipayTradeNo},
status = #{status},
update_time = now()
where order_no = #{orderNo}
and status != 'SUCCESS'
</update>
<update id="updateStatusByOrderNo">
update pay_order
set status = #{status},
update_time = now()
where order_no = #{orderNo}
</update>
<select id="countSuccessRewardByUserId" resultType="int">
select count(1)
from pay_order
where user_id = #{userId}
and status = 'SUCCESS'
</select>
</mapper>