半藏商城中应用消息队列的场景

1、用于解决用户下单以后,订单超时如何取消订单的问题。 - 用户进行提交订单操作(会有锁定商品库存等操作); - 生成订单,获取订单的id; - 获取到设置的订单超时时间(假设设置的为60分钟不支付取消订单); - 按订单超时时间发送一个延迟消息给RabbitMQ,让它在订单超时后触发取消订单的操作; - 如果用户没有支付,进行取消订单操作(释放锁定商品库存一系列操作)。
实现方法 - 需要一个订单延迟消息队列 以及一个取消订单消息队列, - 一旦有消息以延迟订单设置的路由键发送过来,会转发到订单延迟消息队列,并在此队列保存一定时间,等到超时后会自动将消息发送到取消订单消息消费队列。
2、短信验证码以及邮箱验证码都采用消息队列进行消费。 - 采用队列,交换机,路由键进行消费。一条队列,一个交换机,一个路由键就可以实现。

在pom.xml中添加相关依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-amqp</artifactId>
  4. </dependency>

修改SpringBoot配置文件

  1. #SpringBoot配置RabbitMq
  2. rabbitmq:
  3. host: localhost # rabbitmq的连接地址
  4. port: 5672 # rabbitmq的连接端口号
  5. virtual-host: /hanzoMall # rabbitmq的虚拟host
  6. username: hanzoMall # rabbitmq的用户名
  7. password: hanzoMall # rabbitmq的密码
  8. publisher-confirms: true #如果对异步消息需要回调必须设置为true

消息队列的枚举配置类QueueEnum

用于延迟消息队列及处理取消订单消息队列的常量定义,包括交换机名称、队列名称、路由键名称。 以及短信发送消息队列和邮箱验证码发送消息队列的常量定义,包括交换机名称、队列名称、路由键名称。

  1. import com.rabbitmq.client.AMQP;
  2. import lombok.Getter;
  3. /**
  4. * @Author 皓宇QAQ
  5. * @email 2469653218@qq.com
  6. * @Date 2020/5/23 21:00
  7. * @link https://github.com/Tianhaoy/hanzomall
  8. * @Description: 消息队列枚举配置
  9. */
  10. @Getter
  11. public enum QueueEnum {
  12. /**
  13. * 发送短信消息通知队列
  14. */
  15. QUEUE_SMS_SEND("mall.sms.direct", "mall.sms.send", "mall.sms.send"),
  16. /**
  17. * 发送邮件消息通知队列
  18. */
  19. QUEUE_EMAIL_SEND("mall.email.direct", "mall.email.send", "mall.email.send"),
  20. /**
  21. * 消息通知队列
  22. * mall.order.direct(取消订单消息队列所绑定的交换机):绑定的队列为mall.order.cancel,一旦有消息以mall.order.cancel为路由键发过来,会发送到此队列。
  23. */
  24. QUEUE_ORDER_CANCEL("mall.order.direct", "mall.order.cancel", "mall.order.cancel"),
  25. /**
  26. * 消息通知ttl队列
  27. * mall.order.direct.ttl(订单延迟消息队列所绑定的交换机):绑定的队列为mall.order.cancel.ttl,一旦有消息以mall.order.cancel.ttl为路由键发送过来,会转发到此队列,并在此队列保存一定时间,等到超时后会自动将消息发送到mall.order.cancel(取消订单消息消费队列)。
  28. */
  29. QUEUE_TTL_ORDER_CANCEL("mall.order.direct.ttl", "mall.order.cancel.ttl", "mall.order.cancel.ttl");
  30. /**
  31. * 交换机名称
  32. */
  33. private String exchange;
  34. /**
  35. * 队列名称
  36. */
  37. private String name;
  38. /**
  39. * 路由键
  40. */
  41. private String routeKey;
  42. QueueEnum(String exchange, String name, String routeKey) {
  43. this.exchange = exchange;
  44. this.name = name;
  45. this.routeKey = routeKey;
  46. }
  47. }

添加RabbitMq的配置

用于配置交换机、队列及队列与交换机的绑定关系。

  1. import ltd.hanzo.mall.common.QueueEnum;
  2. import org.springframework.amqp.core.*;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. /**
  6. * @Author 皓宇QAQ
  7. * @email 2469653218@qq.com
  8. * @Date 2020/5/23 21:04
  9. * @link https://github.com/Tianhaoy/hanzomall
  10. * @Description: 消息队列配置
  11. */
  12. @Configuration
  13. public class RabbitMqConfig {
  14. /**
  15. * 1.0发送短信消息通知队列所绑定的->交换机
  16. */
  17. @Bean
  18. DirectExchange sendSmsDirect() {
  19. return (DirectExchange) ExchangeBuilder
  20. .directExchange(QueueEnum.QUEUE_SMS_SEND.getExchange())
  21. .durable(true)
  22. .build();
  23. }
  24. /**
  25. * 1.0发送短信的->消费队列
  26. */
  27. @Bean
  28. public Queue sendSmsQueue() {
  29. return new Queue(QueueEnum.QUEUE_SMS_SEND.getName());
  30. }
  31. /**
  32. * 1.0将发送短信 队列绑定到->交换机
  33. */
  34. @Bean
  35. Binding sendSmsBinding(DirectExchange sendSmsDirect, Queue sendSmsQueue){
  36. return BindingBuilder
  37. .bind(sendSmsQueue)
  38. .to(sendSmsDirect)
  39. .with(QueueEnum.QUEUE_SMS_SEND.getRouteKey());
  40. }
  41. /**
  42. * 2.0发送邮件消息通知队列所绑定的->交换机
  43. */
  44. @Bean
  45. DirectExchange sendEmailDirect() {
  46. return (DirectExchange) ExchangeBuilder
  47. .directExchange(QueueEnum.QUEUE_EMAIL_SEND.getExchange())
  48. .durable(true)
  49. .build();
  50. }
  51. /**
  52. * 2.0发送邮件的->消费队列
  53. */
  54. @Bean
  55. public Queue sendEmailQueue() {
  56. return new Queue(QueueEnum.QUEUE_EMAIL_SEND.getName());
  57. }
  58. /**
  59. * 2.0将发送邮件 队列绑定到->交换机
  60. */
  61. @Bean
  62. Binding sendEmailBinding(DirectExchange sendEmailDirect, Queue sendEmailQueue){
  63. return BindingBuilder
  64. .bind(sendEmailQueue)
  65. .to(sendEmailDirect)
  66. .with(QueueEnum.QUEUE_EMAIL_SEND.getRouteKey());
  67. }
  68. /**
  69. * 3.0订单消息实际消费队列所绑定的->交换机
  70. */
  71. @Bean
  72. DirectExchange orderDirect() {
  73. return (DirectExchange) ExchangeBuilder
  74. .directExchange(QueueEnum.QUEUE_ORDER_CANCEL.getExchange())
  75. .durable(true)
  76. .build();
  77. }
  78. /**
  79. * 3.0订单实际消费队列
  80. */
  81. @Bean
  82. public Queue orderQueue() {
  83. return new Queue(QueueEnum.QUEUE_ORDER_CANCEL.getName());
  84. }
  85. /**
  86. * 3.0将订单队列绑定到交换机
  87. */
  88. @Bean
  89. Binding orderBinding(DirectExchange orderDirect,Queue orderQueue){
  90. return BindingBuilder
  91. .bind(orderQueue)
  92. .to(orderDirect)
  93. .with(QueueEnum.QUEUE_ORDER_CANCEL.getRouteKey());
  94. }
  95. /**
  96. * 4.0订单延迟队列队列所绑定的->交换机
  97. */
  98. @Bean
  99. DirectExchange orderTtlDirect() {
  100. return (DirectExchange) ExchangeBuilder
  101. .directExchange(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getExchange())
  102. .durable(true)
  103. .build();
  104. }
  105. /**
  106. * 4.0订单延迟队列(死信队列)
  107. */
  108. @Bean
  109. public Queue orderTtlQueue() {
  110. return QueueBuilder
  111. .durable(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getName())
  112. .withArgument("x-dead-letter-exchange", QueueEnum.QUEUE_ORDER_CANCEL.getExchange())//到期后转发的交换机
  113. .withArgument("x-dead-letter-routing-key", QueueEnum.QUEUE_ORDER_CANCEL.getRouteKey())//到期后转发的路由键
  114. .build();
  115. }
  116. /**
  117. * 4.0将订单延迟队列绑定到交换机
  118. */
  119. @Bean
  120. Binding orderTtlBinding(DirectExchange orderTtlDirect,Queue orderTtlQueue){
  121. return BindingBuilder
  122. .bind(orderTtlQueue)
  123. .to(orderTtlDirect)
  124. .with(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getRouteKey());
  125. }
  126. }

在RabbitMQ管理页面可以看到以下交换机和队列

image.png
image.png

交换机及队列说明

  • mall.order.direct(取消订单消息队列所绑定的交换机):绑定的队列为mall.order.cancel,一旦有消息以mall.order.cancel为路由键发过来,会发送到此队列。
  • mall.order.direct.ttl(订单延迟消息队列所绑定的交换机):绑定的队列为mall.order.cancel.ttl,一旦有消息以mall.order.cancel.ttl为路由键发送过来,会转发到此队列,并在此队列保存一定时间,等到超时后会自动将消息发送到mall.order.cancel(取消订单消息消费队列)。

    添加延迟消息的发送者CancelOrderSender

    用于向订单延迟消息队列(mall.order.cancel.ttl)里发送消息。
  1. import lombok.extern.slf4j.Slf4j;
  2. import ltd.hanzo.mall.common.QueueEnum;
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5. import org.springframework.amqp.AmqpException;
  6. import org.springframework.amqp.core.AmqpTemplate;
  7. import org.springframework.amqp.core.Message;
  8. import org.springframework.amqp.core.MessagePostProcessor;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.stereotype.Component;
  11. /**
  12. * @Author 皓宇QAQ
  13. * @Date 2020/6/6 17:24
  14. * @Description:取消订单消息的发出者
  15. */
  16. @Component
  17. @Slf4j
  18. public class CancelOrderSender {
  19. @Autowired
  20. private AmqpTemplate amqpTemplate;
  21. public void sendMessage(String orderNo,final long delayTimes){
  22. //给延迟队列发送消息
  23. amqpTemplate.convertAndSend(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getExchange(), QueueEnum.QUEUE_TTL_ORDER_CANCEL.getRouteKey(), orderNo, new MessagePostProcessor() {
  24. @Override
  25. public Message postProcessMessage(Message message) throws AmqpException {
  26. //给消息设置延迟毫秒值
  27. message.getMessageProperties().setExpiration(String.valueOf(delayTimes));
  28. return message;
  29. }
  30. });
  31. log.info("send delay message orderNo:{}",orderNo);
  32. }
  33. }

添加取消订单消息的接收者CancelOrderReceiver

用于从取消订单的消息队列(mall.order.cancel)里接收消息。

  1. import lombok.extern.slf4j.Slf4j;
  2. import ltd.hanzo.mall.service.HanZoMallOrderService;
  3. import ltd.hanzo.mall.service.TaskService;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. import org.springframework.amqp.rabbit.annotation.RabbitHandler;
  7. import org.springframework.amqp.rabbit.annotation.RabbitListener;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.stereotype.Component;
  10. /**
  11. * @Author 皓宇QAQ
  12. * @Date 2020/6/6 17:25
  13. * @Description:取消订单消息的处理者
  14. */
  15. @Component
  16. @RabbitListener(queues = "mall.order.cancel")
  17. @Slf4j
  18. public class CancelOrderReceiver {
  19. @Autowired
  20. private HanZoMallOrderService hanZoMallOrderService;
  21. @Autowired
  22. private TaskService taskService;
  23. @RabbitHandler
  24. public void handle(String orderNo){
  25. log.info("receive delay message orderNo:{}",orderNo);
  26. hanZoMallOrderService.cancelOrder(orderNo);
  27. taskService.cancelOrderSendSimpleMail(orderNo);
  28. }
  29. }

添加HanZoMallOrderService接口

  1. public interface HanZoMallOrderService {
  2. /**
  3. * 保存订单
  4. *
  5. * @param user
  6. * @param myShoppingCartItems
  7. * @return
  8. */
  9. String saveOrder(HanZoMallUserVO user, List<HanZoMallShoppingCartItemVO> myShoppingCartItems);
  10. /**
  11. * 取消单个超时订单
  12. */
  13. @Transactional
  14. void cancelOrder(String orderNo);
  15. }

添加HanZoMallOrderService实现类HanZoMallOrderServiceImpl

  1. @Slf4j
  2. @Service
  3. public class HanZoMallOrderServiceImpl implements HanZoMallOrderService {
  4. @Resource
  5. private HanZoMallOrderMapper hanZoMallOrderMapper;
  6. @Resource
  7. private HanZoMallOrderItemMapper hanZoMallOrderItemMapper;
  8. @Resource
  9. private HanZoMallShoppingCartItemMapper hanZoMallShoppingCartItemMapper;
  10. @Resource
  11. private HanZoMallGoodsMapper hanZoMallGoodsMapper;
  12. @Autowired
  13. private CancelOrderSender cancelOrderSender;
  14. @Override
  15. @Transactional
  16. public String saveOrder(HanZoMallUserVO user, List<HanZoMallShoppingCartItemVO> myShoppingCartItems) {
  17. //todo 执行一系类下单操作,代码在github中
  18. //下单完成后开启一个延迟消息,用于当用户没有付款时取消订单
  19. sendDelayMessageCancelOrder(orderNo);
  20. //所有操作成功后,将订单号返回,以供Controller方法跳转到订单详情
  21. return orderNo;
  22. @Override
  23. public void cancelOrder(String orderNo) {
  24. HanZoMallOrder hanZoMallOrder = hanZoMallOrderMapper.selectByOrderNo(orderNo);
  25. if (hanZoMallOrder != null && hanZoMallOrder.getOrderStatus() == 0) {
  26. //超时取消订单
  27. hanZoMallOrderMapper.closeOrder(Collections.singletonList(hanZoMallOrder.getOrderId()), HanZoMallOrderStatusEnum.ORDER_CLOSED_BY_EXPIRED.getOrderStatus());
  28. }
  29. }
  30. private void sendDelayMessageCancelOrder(String orderNo) {
  31. //获取订单超时时间,假设为60分钟
  32. long delayTimes = 36 * 100000;
  33. //发送延迟消息
  34. cancelOrderSender.sendMessage(orderNo, delayTimes);
  35. }

添加OrderController定义接口

  1. import io.swagger.annotations.Api;
  2. import io.swagger.annotations.ApiOperation;
  3. import lombok.extern.slf4j.Slf4j;
  4. import ltd.hanzo.mall.common.*;
  5. import ltd.hanzo.mall.controller.vo.HanZoMallOrderDetailVO;
  6. import ltd.hanzo.mall.controller.vo.HanZoMallOrderItemVO;
  7. import ltd.hanzo.mall.controller.vo.HanZoMallShoppingCartItemVO;
  8. import ltd.hanzo.mall.controller.vo.HanZoMallUserVO;
  9. import ltd.hanzo.mall.entity.HanZoMallOrder;
  10. import ltd.hanzo.mall.service.HanZoMallOrderService;
  11. import ltd.hanzo.mall.service.HanZoMallShoppingCartService;
  12. import ltd.hanzo.mall.service.MailSendService;
  13. import ltd.hanzo.mall.util.PageQueryUtil;
  14. import ltd.hanzo.mall.util.Result;
  15. import ltd.hanzo.mall.util.ResultGenerator;
  16. import org.apache.poi.xssf.streaming.SXSSFRow;
  17. import org.apache.poi.xssf.streaming.SXSSFSheet;
  18. import org.apache.poi.xssf.streaming.SXSSFWorkbook;
  19. import org.springframework.stereotype.Controller;
  20. import org.springframework.util.CollectionUtils;
  21. import org.springframework.util.StringUtils;
  22. import org.springframework.web.bind.annotation.*;
  23. import org.springframework.web.servlet.mvc.support.RedirectAttributes;
  24. import javax.annotation.Resource;
  25. import javax.servlet.ServletOutputStream;
  26. import javax.servlet.http.HttpServletRequest;
  27. import javax.servlet.http.HttpServletResponse;
  28. import javax.servlet.http.HttpSession;
  29. import java.io.IOException;
  30. import java.io.UnsupportedEncodingException;
  31. import java.text.SimpleDateFormat;
  32. import java.util.List;
  33. import java.util.Map;
  34. @Api(tags = "OrderController", description = "用户订单")
  35. @Slf4j
  36. @Controller
  37. public class OrderController {
  38. @Resource
  39. private HanZoMallShoppingCartService hanZoMallShoppingCartService;
  40. @Resource
  41. private HanZoMallOrderService hanZoMallOrderService;
  42. @Resource
  43. private MailSendService mailSendService;
  44. @ApiOperation("某个订单信息")
  45. @GetMapping("/orders/{orderNo}")
  46. public String orderDetailPage(HttpServletRequest request, @PathVariable("orderNo") String orderNo, HttpSession httpSession) {
  47. HanZoMallUserVO user = (HanZoMallUserVO) httpSession.getAttribute(Constants.MALL_USER_SESSION_KEY);
  48. HanZoMallOrderDetailVO orderDetailVO = hanZoMallOrderService.getOrderDetailByOrderNo(orderNo, user.getUserId());
  49. if (orderDetailVO == null) {
  50. return "error/error_5xx";
  51. }
  52. request.setAttribute("orderDetailVO", orderDetailVO);
  53. return "mall/order-detail";
  54. }
  55. @ApiOperation("我的订单信息")
  56. @GetMapping("/orders")
  57. public String orderListPage(@RequestParam Map<String, Object> params, HttpServletRequest request, HttpSession httpSession) {
  58. HanZoMallUserVO user = (HanZoMallUserVO) httpSession.getAttribute(Constants.MALL_USER_SESSION_KEY);
  59. params.put("userId", user.getUserId());
  60. if (StringUtils.isEmpty(params.get("page"))) {
  61. params.put("page", 1);
  62. }
  63. params.put("limit", Constants.ORDER_SEARCH_PAGE_LIMIT);
  64. //封装我的订单数据
  65. PageQueryUtil pageUtil = new PageQueryUtil(params);
  66. request.setAttribute("orderPageResult", hanZoMallOrderService.getMyOrders(pageUtil));
  67. request.setAttribute("path", "orders");
  68. return "mall/my-orders";
  69. }
  70. @ApiOperation("提交订单")
  71. @GetMapping("/saveOrder")
  72. public String saveOrder(HttpSession httpSession) {
  73. HanZoMallUserVO user = (HanZoMallUserVO) httpSession.getAttribute(Constants.MALL_USER_SESSION_KEY);
  74. List<HanZoMallShoppingCartItemVO> myShoppingCartItems = hanZoMallShoppingCartService.getMyShoppingCartItems(user.getUserId());
  75. if (StringUtils.isEmpty(user.getAddress().trim())) {
  76. //无收货地址
  77. HanZoMallException.fail(ServiceResultEnum.NULL_ADDRESS_ERROR.getResult());
  78. }
  79. if (CollectionUtils.isEmpty(myShoppingCartItems)) {
  80. //购物车中无数据则跳转至错误页 可能会出现快速双击 导致两个请求 第二个请求会抛出错误
  81. HanZoMallException.fail(ServiceResultEnum.SHOPPING_ITEM_ERROR.getResult());
  82. }
  83. //保存订单并返回订单号
  84. String saveOrderResult = hanZoMallOrderService.saveOrder(user, myShoppingCartItems);
  85. //跳转到订单详情页
  86. return "redirect:/orders/" + saveOrderResult;
  87. }
  88. @ApiOperation("取消订单")
  89. @PutMapping("/orders/{orderNo}/cancel")
  90. @ResponseBody
  91. public Result cancelOrder(@PathVariable("orderNo") String orderNo, HttpSession httpSession) {
  92. HanZoMallUserVO user = (HanZoMallUserVO) httpSession.getAttribute(Constants.MALL_USER_SESSION_KEY);
  93. String cancelOrderResult = hanZoMallOrderService.cancelOrder(orderNo, user.getUserId());
  94. if (ServiceResultEnum.SUCCESS.getResult().equals(cancelOrderResult)) {
  95. return ResultGenerator.genSuccessResult();
  96. } else {
  97. return ResultGenerator.genFailResult(cancelOrderResult);
  98. }
  99. }
  100. @ApiOperation("确认收货")
  101. @PutMapping("/orders/{orderNo}/finish")
  102. @ResponseBody
  103. public Result finishOrder(@PathVariable("orderNo") String orderNo, HttpSession httpSession) {
  104. HanZoMallUserVO user = (HanZoMallUserVO) httpSession.getAttribute(Constants.MALL_USER_SESSION_KEY);
  105. String finishOrderResult = hanZoMallOrderService.finishOrder(orderNo, user.getUserId());
  106. if (ServiceResultEnum.SUCCESS.getResult().equals(finishOrderResult)) {
  107. return ResultGenerator.genSuccessResult();
  108. } else {
  109. return ResultGenerator.genFailResult(finishOrderResult);
  110. }
  111. }
  112. @ApiOperation("支付前验证状态")
  113. @GetMapping("/selectPayType")
  114. public String selectPayType(HttpServletRequest request, @RequestParam("orderNo") String orderNo, HttpSession httpSession) {
  115. HanZoMallUserVO user = (HanZoMallUserVO) httpSession.getAttribute(Constants.MALL_USER_SESSION_KEY);
  116. HanZoMallOrder hanZoMallOrder = hanZoMallOrderService.getHanZoMallOrderByOrderNo(orderNo);
  117. // 判断订单userId 验证是否是当前userId下的订单,否则报错 by thy 20200301 已添加验证是否同一用户
  118. // 判断订单状态 by thy 20200301 已添加验证状态为待支付 '0'
  119. if (user.getUserId()==hanZoMallOrder.getUserId()&&hanZoMallOrder.getOrderStatus()== HanZoMallOrderStatusEnum.ORDER_PRE_PAY.getOrderStatus()){
  120. log.debug("通过用户状态验证!");
  121. request.setAttribute("orderNo", orderNo);
  122. request.setAttribute("totalPrice", hanZoMallOrder.getTotalPrice());
  123. return "mall/pay-select";
  124. }
  125. return "error/error_5xx";
  126. }
  127. //各种支付
  128. @ApiOperation("跳转支付接口")
  129. @GetMapping("/payPage")
  130. public String payOrder(HttpServletRequest request, @RequestParam("orderNo") String orderNo, HttpSession httpSession, @RequestParam("payType") int payType, RedirectAttributes attributes) {
  131. HanZoMallUserVO user = (HanZoMallUserVO) httpSession.getAttribute(Constants.MALL_USER_SESSION_KEY);
  132. HanZoMallOrder hanZoMallOrder = hanZoMallOrderService.getHanZoMallOrderByOrderNo(orderNo);
  133. List<HanZoMallOrderItemVO> orderItems = hanZoMallOrderService.getOrderItems(hanZoMallOrder.getOrderId());
  134. String itemString = "";
  135. if (!CollectionUtils.isEmpty(orderItems)) {
  136. for (int i = 0; i < orderItems.size(); i++) {
  137. itemString += orderItems.get(i).getGoodsName() + " x " + orderItems.get(i).getGoodsCount() +" ";
  138. }
  139. }
  140. log.debug("订单商品详情 "+itemString);
  141. // 判断订单userId 验证是否是当前userId下的订单,否则报错 by thy 20200301 已添加验证是否同一用户
  142. // 判断订单状态 by thy 20200301 已添加验证状态为待支付 '0'
  143. if (user.getUserId()==hanZoMallOrder.getUserId()&&hanZoMallOrder.getOrderStatus()== HanZoMallOrderStatusEnum.ORDER_PRE_PAY.getOrderStatus()&&hanZoMallOrder.getPayStatus()==HanZoMallOrderStatusEnum.ORDER_PRE_PAY.getOrderStatus()){
  144. log.debug("通过用户状态验证!");
  145. request.setAttribute("orderNo", orderNo);
  146. request.setAttribute("totalPrice", hanZoMallOrder.getTotalPrice());
  147. if (payType == 1) {
  148. //payType == 1为支付宝支付 重定向到支付宝沙箱接口controller 进行支付
  149. //使用RedirectAttributes 拼key和value 自动帮拼到url上 不会出现中文乱码
  150. attributes.addAttribute("orderNo", orderNo);
  151. attributes.addAttribute("totalPrice", hanZoMallOrder.getTotalPrice());
  152. attributes.addAttribute("itemString", itemString);
  153. return "redirect:/alipay/goAlipay";
  154. } else {
  155. return "mall/wxpay";
  156. }
  157. }
  158. return "error/error_5xx";
  159. }
  160. @ApiOperation("微信支付成功确认死接口")
  161. @GetMapping("/paySuccess")
  162. @ResponseBody
  163. public Result paySuccess(@RequestParam("orderNo") String orderNo, @RequestParam("payType") int payType, HttpSession httpSession) {
  164. HanZoMallUserVO user = (HanZoMallUserVO) httpSession.getAttribute(Constants.MALL_USER_SESSION_KEY);
  165. String payResult = hanZoMallOrderService.paySuccess(orderNo, payType,user.getUserId());
  166. if (ServiceResultEnum.SUCCESS.getResult().equals(payResult)) {
  167. String emailAddress = user.getEmailAddress();
  168. if (!"".equals(emailAddress)){
  169. mailSendService.sendSimpleMail(emailAddress, "【半藏商城付款成功】", "您好,订单号为"+orderNo+"的订单通过微信付款成功!");
  170. }
  171. return ResultGenerator.genSuccessResult();
  172. } else {
  173. return ResultGenerator.genFailResult(payResult);
  174. }
  175. }
  176. //导出订单为Excel表格
  177. @ApiOperation("导出我的订单表格")
  178. @GetMapping("/orders/putExcel")
  179. public void ordersExcel(HttpServletResponse response,HttpSession httpSession){
  180. HanZoMallUserVO user = (HanZoMallUserVO) httpSession.getAttribute(Constants.MALL_USER_SESSION_KEY);
  181. List<HanZoMallOrder> orderListVOS = hanZoMallOrderService.getHanZoMallOrderByUserId(user.getUserId().toString());
  182. //创建poi导出数据对象
  183. SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook();
  184. //创建sheet页
  185. SXSSFSheet sheet = sxssfWorkbook.createSheet("我的订单");
  186. //创建表头
  187. SXSSFRow headRow = sheet.createRow(0);
  188. //设置表头信息
  189. headRow.createCell(0).setCellValue("序号");
  190. headRow.createCell(1).setCellValue("订单号");
  191. headRow.createCell(2).setCellValue("商品名称");//可能有多个商品
  192. headRow.createCell(3).setCellValue("消费金额(元)");
  193. headRow.createCell(4).setCellValue("支付状态");
  194. headRow.createCell(5).setCellValue("支付类型");
  195. headRow.createCell(6).setCellValue("支付时间");
  196. headRow.createCell(7).setCellValue("订单状态");
  197. headRow.createCell(8).setCellValue("收货地址");
  198. headRow.createCell(9).setCellValue("创建时间");
  199. //序号
  200. int x= 1; //序号
  201. int j = 0;//从订单详情表中读取商品名称的标识
  202. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//不转换Excel时间是数字
  203. // 遍历上面数据库查到的数据
  204. for (HanZoMallOrder order : orderListVOS) {
  205. //填充数据 从第二行开始
  206. String orderNo = order.getOrderNo();
  207. int TotalPrice = order.getTotalPrice();
  208. String payStatus = PayStatusEnum.getPayStatusEnumByStatus(order.getPayStatus()).getName();
  209. String PayType = PayTypeEnum.getPayTypeEnumByType(order.getPayType()).getName();
  210. String orderStatus = HanZoMallOrderStatusEnum.getHanZoMallOrderStatusEnumByStatus(order.getOrderStatus()).getName();
  211. String userAddress = order.getUserAddress();
  212. String patTime = "";
  213. String createTime = "";
  214. String goodsNames = "";
  215. if (order.getPayTime()!=null && !"".equals(order.getPayTime())){
  216. patTime = sdf.format(order.getPayTime());
  217. }else {
  218. patTime = "无";
  219. }
  220. if (order.getCreateTime()!=null && !"".equals(order.getCreateTime())){
  221. createTime = sdf.format(order.getCreateTime());
  222. }else {
  223. createTime = "无";
  224. }
  225. //还需要查tb_xxx_mall_order_item表读取订单所包含的商品名称 可能有多个
  226. List<HanZoMallOrderItemVO> orderItems = hanZoMallOrderService.getOrderItems(orderListVOS.get(j).getOrderId());
  227. if (orderItems!=null){
  228. StringBuffer sb = new StringBuffer();
  229. for (int k=0;k<orderItems.size();k++){
  230. sb.append(orderItems.get(k).getGoodsName() + " x " +orderItems.get(k).getGoodsCount()+" ");
  231. }
  232. goodsNames = sb.toString();
  233. }
  234. SXSSFRow dataRow = sheet.createRow(sheet.getLastRowNum() + 1);
  235. //开始填充
  236. dataRow.createCell(0).setCellValue(x);
  237. dataRow.createCell(1).setCellValue(orderNo);
  238. dataRow.createCell(2).setCellValue(goodsNames);
  239. dataRow.createCell(3).setCellValue(TotalPrice);
  240. dataRow.createCell(4).setCellValue(payStatus);
  241. dataRow.createCell(5).setCellValue(PayType);
  242. dataRow.createCell(6).setCellValue(patTime);
  243. dataRow.createCell(7).setCellValue(orderStatus);
  244. dataRow.createCell(8).setCellValue(userAddress);
  245. dataRow.createCell(9).setCellValue(createTime);
  246. x++;//序号自增
  247. j++;//查tb_xxx_mall_order_item 根据的orderId自增
  248. }
  249. try {
  250. // 下载导出
  251. String filename = "半藏商城订单明细";
  252. //filename = URLEncoder.encode(filename, "UTF-8");
  253. try {
  254. //避免文件名中文乱码,将UTF8打散重组成ISO-8859-1编码方式
  255. filename = new String (filename.getBytes("UTF8"),"ISO-8859-1");
  256. } catch (UnsupportedEncodingException e) {
  257. e.printStackTrace();
  258. }
  259. // 设置头信息
  260. response.setCharacterEncoding("UTF-8");
  261. response.setContentType("application/vnd.ms-excel");
  262. //让浏览器下载文件,name是上述默认文件下载名
  263. response.setHeader("Content-Disposition", "attachment;filename=" + filename+ ".xlsx");
  264. //创建一个输出流
  265. ServletOutputStream outputStream = response.getOutputStream();
  266. //写入数据
  267. sxssfWorkbook.write(outputStream);
  268. // 关闭
  269. outputStream.close();
  270. sxssfWorkbook.close();
  271. }catch (IOException e){
  272. log.error("导出我的订单报表出错");
  273. e.printStackTrace();
  274. }
  275. }
  276. }

https://github.com/Tianhaoy/hanzomall