refactor(商城): 订单模块

优化未支付自动取消订单功能
master
wayn 5 years ago
parent 645a603f50
commit 09dde7049d

@ -6,7 +6,7 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modules>
<module>waynboot-common</module>
@ -42,6 +42,7 @@
<easypoi.version>4.1.0</easypoi.version>
<jwt.version>3.9.0</jwt.version>
<qiniu.version>[7.2.0, 7.2.99]</qiniu.version>
<mail.version>1.4.7</mail.version>
</properties>
<dependencyManagement>
@ -196,6 +197,11 @@
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>${mail.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

@ -0,0 +1,38 @@
package com.wayn.admin.api.controller.tool;
import com.wayn.common.core.domain.tool.MailConfig;
import com.wayn.common.core.domain.vo.SendMailVO;
import com.wayn.common.core.service.tool.IMailConfigService;
import com.wayn.common.util.R;
import com.wayn.common.util.mail.MailUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("tool/mailConfig")
public class MallConfigController {
@Autowired
private IMailConfigService mailConfigService;
@GetMapping("{id}")
public R info(@PathVariable Long id) {
return R.success().add("data", mailConfigService.getById(id));
}
@PostMapping("update")
public R update(MailConfig mailConfig) {
mailConfig.setId(1L);
mailConfigService.updateById(mailConfig);
return R.success("修改成功");
}
@PostMapping("sendMail")
public R sendMail(SendMailVO mailVO) {
MailConfig mailConfig = mailConfigService.getById(1L);
if (!mailConfigService.checkMailConfig(mailConfig)) {
return R.error("邮件信息未配置完全,请先填写配置信息");
}
MailUtil.sendMail(mailConfig, mailVO, false);
return R.success("发送成功,请等待");
}
}

@ -147,5 +147,10 @@
<artifactId>swagger-annotations</artifactId>
<version>1.5.21</version>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</dependency>
</dependencies>
</project>

@ -0,0 +1,34 @@
package com.wayn.common.core.domain.tool;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
@Data
@TableName("tool_email_config")
public class MailConfig implements Serializable {
private static final long serialVersionUID = -8825288678724602467L;
@TableId(type = IdType.AUTO)
private Long id;
/** 邮件服务器SMTP地址 */
@NotBlank
private String host;
/** 邮件服务器SMTP端口 */
@NotBlank
private String port;
@NotBlank
private String pass;
/** 发件者用户名,默认为发件人邮箱前缀 */
@NotBlank
private String fromUser;
}

@ -0,0 +1,26 @@
package com.wayn.common.core.domain.vo;
import lombok.Data;
import java.io.Serializable;
/**
* VO
*/
@Data
public class SendMailVO implements Serializable {
private static final long serialVersionUID = 3496419936455305502L;
/**
*
*/
private String sendMail;
/**
*
*/
private String title;
/**
*
*/
private String content;
}

@ -0,0 +1,7 @@
package com.wayn.common.core.mapper.tool;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.wayn.common.core.domain.tool.MailConfig;
public interface MailConfigMapper extends BaseMapper<MailConfig> {
}

@ -0,0 +1,10 @@
package com.wayn.common.core.service.tool;
import com.baomidou.mybatisplus.extension.service.IService;
import com.wayn.common.core.domain.tool.MailConfig;
public interface IMailConfigService extends IService<MailConfig> {
boolean checkMailConfig(MailConfig mailConfig);
}

@ -0,0 +1,19 @@
package com.wayn.common.core.service.tool.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wayn.common.core.domain.tool.MailConfig;
import com.wayn.common.core.mapper.tool.MailConfigMapper;
import com.wayn.common.core.service.tool.IMailConfigService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
@Service
public class MailConfigServiceImpl extends ServiceImpl<MailConfigMapper, MailConfig> implements IMailConfigService {
@Override
public boolean checkMailConfig(MailConfig mailConfig) {
return !StringUtils.isEmpty(mailConfig.getFromUser())
&& !StringUtils.isEmpty(mailConfig.getHost())
&& !StringUtils.isEmpty(mailConfig.getPass());
}
}

@ -0,0 +1,46 @@
package com.wayn.common.task;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public abstract class Task implements Delayed, Runnable {
private final String id;
private final long start;
public Task(String id, long delayInMilliseconds) {
this.id = id;
this.start = System.currentTimeMillis() + delayInMilliseconds;
}
public String getId() {
return id;
}
@Override
public long getDelay(TimeUnit unit) {
long diff = this.start - System.currentTimeMillis();
return unit.convert(diff, TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return (int) (this.start - ((Task) o).start);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (!(o instanceof Task)) {
return false;
}
Task t = (Task) o;
return this.id.equals(t.getId());
}
@Override
public int hashCode() {
return this.id.hashCode();
}
}

@ -0,0 +1,39 @@
package com.wayn.common.task;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Executors;
@Component
public class TaskService {
private final DelayQueue<Task> delayQueue = new DelayQueue<>();
@PostConstruct
private void init() {
Executors.newSingleThreadExecutor().execute(() -> {
while (true) {
try {
Task task = delayQueue.take();
task.run();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public void addTask(Task task) {
if (delayQueue.contains(task)) {
return;
}
delayQueue.add(task);
}
public void removeTask(Task task) {
delayQueue.remove(task);
}
}

@ -0,0 +1,67 @@
package com.wayn.common.util.mail;
import com.sun.mail.util.MailSSLSocketFactory;
import com.wayn.common.core.domain.tool.MailConfig;
import com.wayn.common.core.domain.vo.SendMailVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.mail.javamail.MimeMessageHelper;
import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Properties;
/**
*
*/
@Slf4j
public class MailUtil {
public static void sendMail(MailConfig mailConfig, SendMailVO mailVO, boolean isHtml) {
try {
// 设置发件人
String from = mailConfig.getFromUser();
// 设置收件人
String to = mailVO.getSendMail();
// 设置邮件发送的服务器这里为QQ邮件服务器
String host = mailConfig.getHost();
// 获取系统属性
Properties properties = System.getProperties();
// SSL加密
MailSSLSocketFactory sf = new MailSSLSocketFactory();
sf.setTrustAllHosts(true);
properties.put("mail.smtp.ssl.enable", "true");
properties.put("mail.smtp.ssl.socketFactory", sf);
// 设置系统属性
properties.setProperty("mail.smtp.host", host);
properties.put("mail.smtp.auth", "true");
//获取发送邮件会话、获取第三方登录授权码
Session session = Session.getDefaultInstance(properties, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(from, mailConfig.getPass());
}
});
// session.setDebug(true);
// 创建默认的 MimeMessage 对象
MimeMessage message = new MimeMessage(session);
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
// Set From: 头部头字段
helper.setFrom(new InternetAddress(from));
// Set To: 头部头字段
helper.setTo(to);
// Set Subject: 头部头字段
helper.setSubject(mailVO.getTitle());
// 设置消息体
helper.setText(mailVO.getContent(), isHtml);
Transport.send(message);
log.info("邮件发送成功");
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}

@ -10,6 +10,9 @@ import com.wayn.mobile.api.service.IOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RestController
@RequestMapping("order")
public class OrderController extends BaseController {
@ -42,4 +45,10 @@ public class OrderController extends BaseController {
public R h5pay(@RequestBody OrderVO orderVO) {
return iOrderService.h5pay(orderVO.getOrderId(), request);
}
@PostMapping("payNotify")
public R payNotify(HttpServletRequest request, HttpServletResponse response) {
return iOrderService.payNotify(request, response);
}
}

@ -7,6 +7,7 @@ import com.wayn.mobile.api.domain.Order;
import com.wayn.mobile.api.domain.vo.OrderVO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* <p>
@ -49,11 +50,18 @@ public interface IOrderService extends IService<Order> {
R prepay(Long orderId, HttpServletRequest request);
/**
*
* @param page
* @param showType
*
* @param page
* @param showType
* @return r
*/
R selectListPage(IPage<Order> page, Integer showType);
/**
*
* @param request
* @param response
* @return r
*/
R payNotify(HttpServletRequest request, HttpServletResponse response);
}

@ -3,19 +3,29 @@ package com.wayn.mobile.api.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
import com.github.binarywang.wxpay.bean.order.WxPayMwebOrderResult;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.wayn.common.core.domain.shop.Address;
import com.wayn.common.core.domain.shop.GoodsProduct;
import com.wayn.common.core.domain.shop.Member;
import com.wayn.common.core.domain.tool.MailConfig;
import com.wayn.common.core.domain.vo.SendMailVO;
import com.wayn.common.core.service.shop.IAddressService;
import com.wayn.common.core.service.shop.IGoodsProductService;
import com.wayn.common.core.service.shop.IMemberService;
import com.wayn.common.core.service.tool.IMailConfigService;
import com.wayn.common.exception.BusinessException;
import com.wayn.common.task.TaskService;
import com.wayn.common.util.R;
import com.wayn.common.util.ip.IpUtils;
import com.wayn.common.util.mail.MailUtil;
import com.wayn.mobile.api.domain.Cart;
import com.wayn.mobile.api.domain.Order;
import com.wayn.mobile.api.domain.OrderGoods;
@ -27,19 +37,21 @@ import com.wayn.mobile.api.service.IOrderService;
import com.wayn.mobile.api.task.CancelOrderTask;
import com.wayn.mobile.api.util.OrderHandleOption;
import com.wayn.mobile.api.util.OrderUtil;
import com.wayn.mobile.framework.manager.thread.AsyncManager;
import com.wayn.mobile.framework.redis.RedisCache;
import com.wayn.mobile.framework.security.util.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* <p>
@ -49,6 +61,7 @@ import java.util.concurrent.TimeUnit;
* @author wayn
* @since 2020-08-11
*/
@Slf4j
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
@ -73,9 +86,14 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
@Autowired
private IMemberService iMemberService;
@Autowired
private IMailConfigService mailConfigService;
@Autowired
private OrderMapper orderMapper;
@Autowired
private TaskService taskService;
@Override
public R selectListPage(IPage<Order> page, Integer showType) {
@ -112,7 +130,6 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
return R.success().add("data", orderVoList).add("pages", orderIPage.getPages()).add("page", orderIPage.getCurrent());
}
@Override
@Transactional(rollbackFor = Exception.class)
public R addOrder(OrderVO orderVO) {
@ -221,9 +238,10 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
if (!iGoodsProductService.reduceStock(productId, checkGoods.getNumber())) {
throw new BusinessException("商品货品库存减少失败");
}
long delay = 5;
redisCache.setCacheZset("order_zset", order.getId(), System.currentTimeMillis() / 1000 + 60 * delay);
AsyncManager.me().execute(new CancelOrderTask(order.getId()), delay, TimeUnit.MINUTES);
long delay = 1000;
redisCache.setCacheZset("order_zset", order.getId(), System.currentTimeMillis() + 60 * delay);
taskService.addTask(new CancelOrderTask(order.getId(), delay * 60));
// AsyncManager.me().execute(new CancelOrderTask(order.getId()), delay, TimeUnit.MINUTES);
}
return R.success().add("orderId", order.getId());
} else {
@ -287,7 +305,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
return R.error("订单不能支付");
}
WxPayMwebOrderResult result = null;
WxPayMwebOrderResult result;
try {
WxPayUnifiedOrderRequest orderRequest = new WxPayUnifiedOrderRequest();
orderRequest.setOutTradeNo(order.getOrderSn());
@ -300,11 +318,81 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
orderRequest.setTotalFee(fee);
orderRequest.setSpbillCreateIp(IpUtils.getIpAddr(request));
result = wxPayService.createOrder(orderRequest);
return R.success().add("data", result);
} catch (Exception e) {
log.error(e.getMessage(), e);
return R.error("支付失败");
}
return R.success().add("data", result);
}
@Override
public R payNotify(HttpServletRequest request, HttpServletResponse response) {
String xmlResult;
try {
xmlResult = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
} catch (IOException e) {
e.printStackTrace();
return R.error(WxPayNotifyResponse.fail(e.getMessage()));
}
WxPayOrderNotifyResult result;
try {
result = wxPayService.parseOrderNotifyResult(xmlResult);
if (!WxPayConstants.ResultCode.SUCCESS.equals(result.getResultCode())) {
log.error(xmlResult);
throw new WxPayException("微信通知支付失败!");
}
} catch (WxPayException e) {
e.printStackTrace();
return R.error(WxPayNotifyResponse.fail(e.getMessage()));
}
log.info("处理腾讯支付平台的订单支付");
log.info(result.getReturnMsg());
String orderSn = result.getOutTradeNo();
String payId = result.getTransactionId();
// 分转化成元
String totalFee = BaseWxPayResult.fenToYuan(result.getTotalFee());
Order order = getOne(new QueryWrapper<Order>().eq("order_sn", orderSn));
if (order == null) {
return R.error(WxPayNotifyResponse.fail("订单不存在 sn=" + orderSn));
}
// 检查这个订单是否已经处理过
if (OrderUtil.hasPayed(order)) {
return R.error(WxPayNotifyResponse.success("订单已经处理成功!"));
}
// 检查支付订单金额
if (!totalFee.equals(order.getActualPrice().toString())) {
return R.error(WxPayNotifyResponse.fail(order.getOrderSn() + " : 支付金额不符合 totalFee=" + totalFee));
}
order.setPayId(payId);
order.setPayTime(LocalDateTime.now());
order.setOrderStatus(OrderUtil.STATUS_PAY);
order.setUpdateTime(LocalDateTime.now());
if (!updateById(order)) {
return R.error(WxPayNotifyResponse.fail("更新数据已失效"));
}
//TODO 发送邮件和短信通知,这里采用异步发送
// 订单支付成功以后,会发送短信给用户,以及发送邮件给管理员
MailConfig mailConfig = mailConfigService.getById(1L);
SendMailVO sendMailVO = new SendMailVO();
sendMailVO.setTitle("新订单通知");
sendMailVO.setContent(order.toString());
sendMailVO.setSendMail("1669738430@qq.com");
MailUtil.sendMail(mailConfig, sendMailVO, false);
// 删除redis中订单id
redisCache.deleteZsetObject("order_zset", order.getId());
// 取消订单超时未支付任务
taskService.removeTask(new CancelOrderTask(order.getId()));
return R.error(WxPayNotifyResponse.success("处理成功!"));
}
}

@ -2,6 +2,7 @@ package com.wayn.mobile.api.task;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wayn.common.core.service.shop.IGoodsProductService;
import com.wayn.common.task.Task;
import com.wayn.common.util.spring.SpringContextUtil;
import com.wayn.mobile.api.domain.Order;
import com.wayn.mobile.api.domain.OrderGoods;
@ -16,17 +17,30 @@ import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.TimerTask;
/**
*
*/
@Slf4j
public class CancelOrderTask extends TimerTask {
public class CancelOrderTask extends Task {
/**
*
*/
private static final long DELAY_TIME = 30 * 60 * 1000;
/**
* id
*/
private final Long orderId;
public CancelOrderTask(Long orderId, long delayInMilliseconds) {
super("CancelOrderTask-" + orderId, delayInMilliseconds);
this.orderId = orderId;
}
public CancelOrderTask(Long orderId) {
super("CancelOrderTask-" + orderId, DELAY_TIME);
this.orderId = orderId;
}
@ -37,13 +51,13 @@ public class CancelOrderTask extends TimerTask {
IOrderGoodsService orderGoodsService = SpringContextUtil.getBean(IOrderGoodsService.class);
IGoodsProductService productService = SpringContextUtil.getBean(IGoodsProductService.class);
RedisCache redisCache = SpringContextUtil.getBean(RedisCache.class);
Set<Long> zset = redisCache.getCacheZset("order_zset", 0, System.currentTimeMillis() / 1000);
Set<Long> zset = redisCache.getCacheZset("order_zset", 0, System.currentTimeMillis());
if (CollectionUtils.isNotEmpty(zset) && zset.contains(this.orderId)) {
for (Long orderId : zset) {
log.info("系统开始处理延时任务---redis内超时未付款---" + orderId);
log.info("redis内未付款---" + orderId);
final Long num = redisCache.deleteZsetObject("order_zset", orderId);
if (num != null && num > 0) {
Order order = orderService.getOne(new QueryWrapper<Order>().eq("order_status", OrderUtil.STATUS_AUTO_CANCEL).eq("id", orderId));
Order order = orderService.getOne(new QueryWrapper<Order>().eq("order_status", OrderUtil.STATUS_CREATE).eq("id", orderId));
if (Objects.isNull(order) || !OrderUtil.isCreateStatus(order)) {
return;
}
@ -66,7 +80,7 @@ public class CancelOrderTask extends TimerTask {
}
}
}
log.info("系统结束处理延时任务---redis内超时未付款---" + orderId);
log.info("redis内未付款---" + orderId);
}
}
log.info("系统结束处理延时任务---订单超时未付款---" + this.orderId);

Loading…
Cancel
Save