Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)二十(下单)

0.学习目标

  • 会调用订单系统接口
  • 实现订单结算功能
  • 实现微信支付功能

1.订单系统接口

1.1.创建订单微服务

1.1.1创建model

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.1.2 在pom.xml引入依赖

在这里插入图片描述

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>leyou</artifactId>
        <groupId>com.leyou.parent</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.leyou.service</groupId>
    <artifactId>ly-order</artifactId>


    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- mybatis启动器 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <!-- 通用Mapper启动器 -->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
        </dependency>
        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.leyou.service</groupId>
            <artifactId>ly-item-interface</artifactId>
            <version>${leyou.latest.version}</version>
        </dependency>
        <dependency>
            <groupId>com.leyou.common</groupId>
            <artifactId>ly-common</artifactId>
            <version>${leyou.latest.version}</version>
        </dependency>
        <dependency>
            <groupId>com.leyou.auth</groupId>
            <artifactId>ly-auth-common</artifactId>
            <version>${leyou.latest.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.wxpay</groupId>
            <artifactId>wxpay-sdk</artifactId>
            <version>0.0.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.8.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.8.0</version>
        </dependency>
    </dependencies>

</project>

1.1.3 创建配置文件

在这里插入图片描述
在这里插入图片描述

server:
  port: 8071
spring:
  application:
    name: order-service
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/yun6
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
  jackson:
    default-property-inclusion: non_null
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
    registry-fetch-interval-seconds: 5
  instance:
    ip-address: 127.0.0.1
    prefer-ip-address: true
mybatis:
  type-aliases-package: com.leyou.order.pojo

创建上述对应的别名包
在这里插入图片描述

1.1.4 创建启动类

在这里插入图片描述
在这里插入图片描述

package com.leyou;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import tk.mybatis.spring.annotation.MapperScan;

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@MapperScan("com.leyou.order.mapper")
public class LyOrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(LyOrderApplication.class,args);
    }

}

1.1.5 配置网关

在这里插入图片描述

    order-service: /order/**

1.1.6 公钥配置

在这里插入图片描述

ly:
  jwt:
    pubKeyPath: C:\\Users\\ZHENG\\Desktop\\leyou_msgrs\\rsa\\rsa.pub # 公钥地址
    cookieName: LY_TOKEN # cookie的名称

1.1.7 判断登录用户拦截器相关配置

在这里插入图片描述
在这里插入图片描述

package com.leyou.order.config;

import com.leyou.auth.utils.RsaUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;

import javax.annotation.PostConstruct;
import java.security.PublicKey;

@Data
@ConfigurationProperties(prefix = "ly.jwt")
@Slf4j
public class JwtProperties {

    private String pubKeyPath;// 公钥

    private PublicKey publicKey; // 公钥

    private String cookieName;
    @PostConstruct
    public void init(){
        try {
            // 获取公钥和私钥
            this.publicKey = RsaUtils.getPublicKey(pubKeyPath);
        } catch (Exception e) {
            log.error("初始化公钥失败!", e);
            throw new RuntimeException();
        }
    }

}

在这里插入图片描述
在这里插入图片描述

package com.leyou.order.interceptors;


import com.leyou.auth.entity.UserInfo;
import com.leyou.auth.utils.JwtUtils;
import com.leyou.common.utils.CookieUtils;
import com.leyou.order.config.JwtProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
public class UserInterceptor implements HandlerInterceptor {

    private JwtProperties prop;

    private static final ThreadLocal<UserInfo> tl = new ThreadLocal<>();

    public UserInterceptor(JwtProperties prop) {
        this.prop  = prop;
    }

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        //获取Cookie
        String token = CookieUtils.getCookieValue(request, prop.getCookieName());
        try {
            //解析token
            UserInfo user = JwtUtils.getUserInfo(prop.getPublicKey(), token);
            //传递User
            tl.set(user);
            //放行
            return true;
        } catch (Exception e) {
            log.error("[购物车服务]解析用户身份失败", e);
            return false;
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler, Exception ex)
            throws Exception {
        tl.remove();
    }

    public static UserInfo getLoginUser() {
        return tl.get();
    }

}

在这里插入图片描述
在这里插入图片描述

package com.leyou.order.config;
import com.leyou.order.interceptors.UserInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableConfigurationProperties(JwtProperties.class)
public class MvcConfig implements WebMvcConfigurer {

    @Autowired
    private JwtProperties prop;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserInterceptor(prop)).addPathPatterns("/**");
    }
}

1.2.数据库设计

DROP TABLE IF EXISTS `tb_order`;
CREATE TABLE `tb_order` (
  `order_id` bigint(20) NOT NULL COMMENT '订单id',
  `total_pay` bigint(20) NOT NULL COMMENT '总金额,单位为分',
  `actual_pay` bigint(20) NOT NULL COMMENT '实付金额。单位:分。如:20007,表示:200元7分',
  `promotion_ids` varchar(256) COLLATE utf8_bin DEFAULT '',
  `payment_type` tinyint(1) unsigned zerofill NOT NULL COMMENT '支付类型,1、在线支付,2、货到付款',
  `post_fee` bigint(20) NOT NULL COMMENT '邮费。单位:分。如:20007,表示:200元7分',
  `create_time` datetime DEFAULT NULL COMMENT '订单创建时间',
  `shipping_name` varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT '物流名称',
  `shipping_code` varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT '物流单号',
  `user_id` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '用户id',
  `buyer_message` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT '买家留言',
  `buyer_nick` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '买家昵称',
  `buyer_rate` tinyint(1) DEFAULT NULL COMMENT '买家是否已经评价,0未评价,1已评价',
  `receiver_state` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '收获地址(省)',
  `receiver_city` varchar(256) COLLATE utf8_bin DEFAULT '' COMMENT '收获地址(市)',
  `receiver_district` varchar(256) COLLATE utf8_bin DEFAULT '' COMMENT '收获地址(区/县)',
  `receiver_address` varchar(256) COLLATE utf8_bin DEFAULT '' COMMENT '收获地址(街道、住址等详细地址)',
  `receiver_mobile` varchar(11) COLLATE utf8_bin DEFAULT NULL COMMENT '收货人手机',
  `receiver_zip` varchar(16) COLLATE utf8_bin DEFAULT NULL COMMENT '收货人邮编',
  `receiver` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT '收货人',
  `invoice_type` int(1) DEFAULT '0' COMMENT '发票类型(0无发票1普通发票,2电子发票,3增值税发票)',
  `source_type` int(1) DEFAULT '2' COMMENT '订单来源:1:app端,2:pc端,3:M端,4:微信端,5:手机qq端',
  PRIMARY KEY (`order_id`) USING BTREE,
  KEY `create_time` (`create_time`) USING BTREE,
  KEY `buyer_nick` (`buyer_nick`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=DYNAMIC;
DROP TABLE IF EXISTS `tb_order_detail`;
CREATE TABLE `tb_order_detail` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单详情id ',
  `order_id` bigint(20) NOT NULL COMMENT '订单id',
  `sku_id` bigint(20) NOT NULL COMMENT 'sku商品id',
  `num` int(11) NOT NULL COMMENT '购买数量',
  `title` varchar(256) NOT NULL COMMENT '商品标题',
  `own_spec` varchar(1024) DEFAULT '' COMMENT '商品动态属性键值集',
  `price` bigint(20) NOT NULL COMMENT '价格,单位:分',
  `image` varchar(128) DEFAULT '' COMMENT '商品图片',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `key_order_id` (`order_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=126 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='订单详情表';

DROP TABLE IF EXISTS `tb_order_status`;
CREATE TABLE `tb_order_status` (
  `order_id` bigint(20) NOT NULL COMMENT '订单id',
  `status` int(1) DEFAULT NULL COMMENT '状态:1、未付款 2、已付款,未发货 3、已发货,未确认 4、交易成功 5、交易关闭 6、已评价',
  `create_time` datetime DEFAULT NULL COMMENT '订单创建时间',
  `payment_time` datetime DEFAULT NULL COMMENT '付款时间',
  `consign_time` datetime DEFAULT NULL COMMENT '发货时间',
  `end_time` datetime DEFAULT NULL COMMENT '交易完成时间',
  `close_time` datetime DEFAULT NULL COMMENT '交易关闭时间',
  `comment_time` datetime DEFAULT NULL COMMENT '评价时间',
  PRIMARY KEY (`order_id`) USING BTREE,
  KEY `status` (`status`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='订单状态表';

1.3.相关实体类

在这里插入图片描述

package com.leyou.order.pojo;

import lombok.Data;

import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import java.util.Date;
import java.util.List;

@Data
@Table(name = "tb_order")
public class Order {

    @Id
    private Long orderId;// id
    private Long totalPay;// 总金额
    private Long actualPay;// 实付金额
    private Integer paymentType; // 支付类型,1、在线支付,2、货到付款

    private String promotionIds; // 参与促销活动的id
    private Long postFee = 0L;// 邮费
    private Date createTime;// 创建时间
    private String shippingName;// 物流名称
    private String shippingCode;// 物流单号
    private Long userId;// 用户id
    private String buyerMessage;// 买家留言
    private String buyerNick;// 买家昵称
    private Boolean buyerRate;// 买家是否已经评价
    private String receiver; // 收货人全名
    private String receiverMobile; // 移动电话
    private String receiverState; // 省份
    private String receiverCity; // 城市
    private String receiverDistrict; // 区/县
    private String receiverAddress; // 收货地址,如:xx路xx号
    private String receiverZip; // 邮政编码,如:310001
    private Integer invoiceType = 0;// 发票类型,0无发票,1普通发票,2电子发票,3增值税发票
    private Integer sourceType = 1;// 订单来源 1:app端,2:pc端,3:M端,4:微信端,5:手机qq端

    @Transient
    private OrderStatus orderStatus;

    @Transient
    private List<OrderDetail> orderDetails;
}

在这里插入图片描述

package com.leyou.order.pojo;

import lombok.Data;
import tk.mybatis.mapper.annotation.KeySql;

import javax.persistence.Id;
import javax.persistence.Table;

@Data
@Table(name = "tb_order_detail")
public class OrderDetail {

    @Id
    @KeySql(useGeneratedKeys = true)
    private Long id;

    private Long orderId;// 订单id

    private Long skuId;// 商品id

    private Integer num;// 商品购买数量

    private String title;// 商品标题

    private Long price;// 商品单价

    private String ownSpec;// 商品规格数据

    private String image;// 图片
}

在这里插入图片描述

package com.leyou.order.pojo;

import lombok.Data;

import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;

@Data
@Table(name = "tb_order_status")
public class OrderStatus {

    @Id
    private Long orderId;
    
    private Integer status;

    private Date createTime;// 创建时间

    private Date paymentTime;// 付款时间

    private Date consignTime;// 发货时间

    private Date endTime;// 交易结束时间

    private Date closeTime;// 交易关闭时间

    private Date commentTime;// 评价时间
}

1.4.相关数据传输对象DTO

在这里插入图片描述

在这里插入图片描述

package com.leyou.order.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class CartDTO {

    private Long skuId;//商品skuId
    private Integer num;//购买数量

}

在这里插入图片描述
在这里插入图片描述



@Data
@AllArgsConstructor
@NoArgsConstructor
public class OrderDTO {//数据传输对象
    @NonNull  //@NonNull校验不能为空
    private Long addressId;
    @NonNull
    private Integer paymentType;
    @NonNull
    private List<CartDTO> carts;

}

1.5.相关Controller

在这里插入图片描述
在这里插入图片描述

package com.leyou.order.web;

import com.leyou.order.dto.OrderDTO;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("order")
public class OrderController {

    /*
    创建订单
     */
    @PostMapping
    public ResponseEntity<Long> createOrder(@RequestBody OrderDTO orderDTO){


        return null;

    }

}

1.6.相关Service

在这里插入图片描述
在这里插入图片描述

package com.leyou.order.service;

import org.springframework.stereotype.Service;

@Service
public class OrderService {


}

1.7.相关Mapper

在这里插入图片描述
在这里插入图片描述

package com.leyou.order.mapper;

import com.leyou.common.mapper.BaseMapper;
import com.leyou.order.pojo.Order;

public interface OrderMapper extends BaseMapper<Order> {

}

在这里插入图片描述
在这里插入图片描述

package com.leyou.order.mapper;

import com.leyou.common.mapper.BaseMapper;
import com.leyou.order.pojo.OrderDetail;

public interface OrderDetailMapper extends BaseMapper<OrderDetail> {

    
}

在这里插入图片描述
在这里插入图片描述

package com.leyou.order.mapper;

import com.leyou.common.mapper.BaseMapper;
import com.leyou.order.pojo.OrderStatus;

public interface OrderStatusMapper extends BaseMapper<OrderStatus> {
}

1.8.完善OrderService

在这里插入图片描述

package com.leyou.order.service;

import com.leyou.order.mapper.OrderDetailMapper;
import com.leyou.order.mapper.OrderMapper;
import com.leyou.order.mapper.OrderStatusMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private OrderDetailMapper detailMapper;

    @Autowired
    private OrderStatusMapper statusMapper;


}

1.9.完善OrderController

在这里插入图片描述

2.0.实现orderService的createOrder方法

在这里插入图片描述
创建订单逻辑比较复杂,需要组装订单数据,基本步骤如下:

  • 获取登录用户信息
  • 生成订单编号,初始化订单基本信息
  • 查询收货人信息
  • 查询商品信息
  • 封装OrderDetail信息
  • 计算总金额、实付金额
  • 保存订单状态信息
  • 删除购物车中已购买商品减库存

2.0.1生成订单编号(雪花算法)

雪花算法是由Twitter公司开源的snowflake(雪花)算法。

  • 雪花算法的原理

雪花算法会生成一个64位的二进制数据,为一个Long型。

(转换成字符串后长度最多19),其基本结构:

在这里插入图片描述

第一位:为未使用

第二部分:41位为毫秒级时间(41位的长度可以使用69年)

第三部分:5位datacenterld和5位workerld(10位的长度最多支持部署1024个节点)

第四部分:最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)

snowflake生成的ID整体上按照时间自增排序,
并且整个分布式系统内不会产生ID碰撞(由datacenterworkerld作区分),
并且效率较高。

经测试snowflake每秒能够产生26万个ID。

  • 配置
    在这里插入图片描述
ly:
  worker:
    workerId : 1
    dataCenterId : 1
  • 加载属性

在这里插入图片描述
在这里插入图片描述

package com.leyou.order.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@ConfigurationProperties(prefix = "ly.worker")
public class IdWorkerProperties {

    private long workerId;
    private long dataCenterId;
    
}

  • 编写配置类
    在这里插入图片描述
    在这里插入图片描述
package com.leyou.order.config;

import com.leyou.common.utils.IdWorker;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(IdWorkerProperties.class)
public class IdWorkerConfig {
    @Bean
    public IdWorker idWorker(IdWorkerProperties prop){
        return new IdWorker(prop.getWorkerId(),prop.getDataCenterId());
    }
}

2.0.2 准备假物流数据

我们前端页面传来的是addressld,我们需要根据id查询物流信息,
但是因为还没做物流地址管理。

所以我们准备一些假数据。

首先是实体类:
在这里插入图片描述
在这里插入图片描述

package com.leyou.order.dto;

import lombok.Data;

@Data
public class AddressDTO {
    private Long id;
    private String name;//收件人姓名
    private String phone;//电话
    private String state;//省份
    private String city;//城市
    private String district;//区
    private String address;//街道地址
    private String zipCode;//邮编
    private Boolean isDefault;
}

在这里插入图片描述
在这里插入图片描述

package com.leyou.order.client;

import com.leyou.order.dto.AddressDTO;

import java.util.ArrayList;
import java.util.List;

public abstract class AddressClient {

    public static final List<AddressDTO> addressList = new ArrayList<AddressDTO>() {
        {
            AddressDTO address = new AddressDTO();
            address.setId(1L);
            address.setAddress("太阳系");
            address.setCity("银河系");
            address.setDistrict("火星");
            address.setName("大哥");
            address.setPhone("15800000000");
            address.setState("阿拉比亚大陆(Arabia Terra)");
            address.setZipCode("210000" ) ;
            address.setIsDefault(true) ;
            add(address);
            AddressDTO address2 = new AddressDTO();address2.setId(2L);
            address.setAddress("太阳系");
            address.setCity("银河系");
            address2.setDistrict("天王星");
            address2.setName("张三");
            address2.setPhone("13600000000" );
            address2.setState("北京");
            address2.setZipCode("100000");
            address2.setIsDefault(false);
            add(address2);
        }
    };

    public static AddressDTO findById(Long id) {
        for (AddressDTO addressDTO : addressList) {
            if (addressDTO.getId() == id)
                return addressDTO;
        }
        return null;

    }
}

2.0.3 在ly-item的ly-item-interface当中新增通过sku的id集合查询其所有sku的接口

在这里插入图片描述

    /*
   通过sku的id集合查询其所有sku
    */
    @GetMapping("sku/list/ids")
    List<Sku> querySkuBySpuId(@RequestParam("ids") List<Long> ids);

2.0.4 在Order当中定义对应client使用上述的接口

在这里插入图片描述
在这里插入图片描述

package com.leyou.order.client;

import com.leyou.item.api.GoodsApi;
import org.springframework.cloud.openfeign.FeignClient;

@FeignClient("item-service")
public interface GoodsClient extends GoodsApi {

    

}

2.0.5 完善OrderService相关的工具类

2.0.5.1 定义异常枚举

在这里插入图片描述

    CREATE_ORDER_ERROR(500,"创建订单失败"),
2.0.5.2 定义订单状态枚举

在这里插入图片描述
在这里插入图片描述

package com.leyou.order.enums;

public enum OrderStatusEnum {
    UN_PAY(1,"未付款"),
    PAYED(2,"已付款,未发货"),
    DELIVERED(3,"已发货,未确认"),
    SUCCESS(4,"已确认,未评价"),
    CLOSED(5,"已关闭,交易失败"),
    RATED(6,"已评价"),
    ;
    private int code;
    private String desc;
    OrderStatusEnum(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }
    public int value(){
        return this.code;
    }
}

2.0.5.3 在StockMapper当中编写键库存的代码

在这里插入图片描述

public interface StockMapper extends BaseMapper<Stock>{

    @Update("UPDATE tb_stock SET stock = stock - #{num} WHERE sku_id = #{id} AND stock >= #{num}")
    int decreaseStock(@Param("id") Long id, @Param("num") Integer num);

}

这里减库存并没有采用先查询库存,判断充足才减库存的方案,那样会有线程安全问题,当然可以通过加锁解决。不过我们此处为了效率,并没有使用悲观锁,而是对库存采用了乐观锁方案

2.0.5.4 在商品微服务当中定义减库存的内容
  • 在ly-common当中创建CartDTO,将下面order当中CartDTO复制一份到ly-common当中
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • GoodsController当中创建decreaseStock方法
    在这里插入图片描述
 /*
    减库存
     */
    @PostMapping("stock/decrease")
    public ResponseEntity<Void> decreaseStock(@RequestBody List<CartDTO> carts){
        goodsService.decreaseStock(carts);
        return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
    }
  • GoodsService
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    @Transactional
    public void decreaseStock(List<CartDTO> carts) {
        for (CartDTO cart : carts) {
           //键库存
            int count = stockMapper.decreaseStock(cart.getSkuId(), cart.getNum());
            if(count != 1){
                throw new LyException(ExceptionEnum.STOCK_NOT_ENOUGH);
            }
        }
    }

  • 测试

在这里插入图片描述

package com.leyou.item.service;

import com.leyou.common.dto.CartDTO;
import com.leyou.item.api.GoodsApi;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class GoodsServiceTest {

    @Autowired
    private GoodsService goodsService;

    @Test
    public void  decreaseStock(){

        List<CartDTO> cartDTOS =
                Arrays.asList(
                        new CartDTO(2600242L, 2),
                        new CartDTO(2600248L, 2)
                );
        goodsService.decreaseStock(cartDTOS);

    }

}

在这里插入图片描述
运行成功
在这里插入图片描述
在这里插入图片描述

2.0.5.5 将接口提供到API当中

在这里插入图片描述

 /*
    减库存
     */
    @PostMapping("stock/decrease")
    void decreaseStock(@RequestBody List<CartDTO> carts);

2.0.6 完善OrderService的createOrder方法

在这里插入图片描述

package com.leyou.order.service;

import com.leyou.auth.entity.UserInfo;
import com.leyou.common.dto.CartDTO;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.IdWorker;
import com.leyou.item.pojo.Sku;
import com.leyou.order.client.AddressClient;
import com.leyou.order.client.GoodsClient;
import com.leyou.order.dto.AddressDTO;
import com.leyou.order.dto.OrderDTO;
import com.leyou.order.enums.OrderStatusEnum;
import com.leyou.order.interceptors.UserInterceptor;
import com.leyou.order.mapper.OrderDetailMapper;
import com.leyou.order.mapper.OrderMapper;
import com.leyou.order.mapper.OrderStatusMapper;
import com.leyou.order.pojo.Order;
import com.leyou.order.pojo.OrderDetail;
import com.leyou.order.pojo.OrderStatus;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j

public class OrderService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private OrderDetailMapper detailMapper;
    @Autowired
    private OrderStatusMapper statusMapper;

    @Autowired
    private IdWorker idWorker;

    @Autowired
    private GoodsClient goodsClient;

    @Transactional
    public Long createOrder(OrderDTO orderDTO) {

        //新增订单
        Order order = new Order();
        //订单编号
        long orderId = idWorker.nextId();
        order.setOrderId(orderId);
        order.setCreateTime(new Date());
        order.setPaymentType(orderDTO.getPaymentType());
        //用户信息
        UserInfo user = UserInterceptor.getLoginUser();
        order.setUserId(user.getId());
        order.setBuyerNick(user.getUserName());
        order.setBuyerRate(false);

        //收货人地址信息
        //获取收货人信息
        AddressDTO addr = AddressClient.findById(orderDTO.getAddressId());
        order.setReceiver(addr.getName());
        order.setReceiverAddress(addr.getAddress());
        order.setReceiverCity(addr.getCity());
        order.setReceiverDistrict(addr.getDistrict());
        order.setReceiverMobile(addr.getPhone());
        order.setReceiverState(addr.getState());
        order.setReceiverZip(addr.getZipCode());
        //金额相关
        //把CartDTO 转为一个map,key是sku的id,值是num
        Map<Long, Integer> numMap = orderDTO.getCarts().stream()
                .collect(Collectors.toMap(CartDTO::getSkuId, CartDTO::getNum));
        //获取所有的sku的id
        Set<Long> ids = numMap.keySet();
        //根据id查询sku
        List<Sku> skus = goodsClient.querySkuBySpuId(new ArrayList<>(ids));
        //准备orderDetail
        List<OrderDetail> details = new ArrayList<>();

        long totalPay = 0L;
        for (Sku sku : skus) {
            //计算商品总价
            totalPay += sku.getPrice() * numMap.get(sku.getId());
            //封装OrderDetail
            OrderDetail detail = new OrderDetail();
            detail.setImage(StringUtils.substringBefore(sku.getImages(),","));
            detail.setNum(numMap.get(sku.getId()));
            detail.setOrderId(orderId);
            detail.setOwnSpec(sku.getOwnSpec());
            detail.setPrice(sku.getPrice());
            detail.setSkuId(sku.getId());
            detail.setTitle(sku.getTitle());
            details.add(detail);
        }
        //总金额
        order.setTotalPay(totalPay);
        //实付金额:总金额+邮费-优惠金额
        order.setActualPay(totalPay + order.getPostFee() - 0);
        //把order写入数据库
        int count = orderMapper.insertSelective(order);
        if(count != 1){
            log.error("[创建订单] 创建订单失败,orderId:{}",orderId);
            throw  new LyException(ExceptionEnum.CREATE_ORDER_ERROR);
        }
        //新增订单详情
        count = detailMapper.insertList(details);
        if(count != details.size()){
            log.error("[创建订单状态] 创建订单失败,orderId:{}",orderId);
            throw  new LyException(ExceptionEnum.CREATE_ORDER_ERROR);
        }
        //新增订单状态
        OrderStatus orderStatus = new OrderStatus();
        orderStatus.setCreateTime(order.getCreateTime());
        orderStatus.setOrderId(orderId);
        orderStatus.setStatus(OrderStatusEnum.UN_PAY.value());
        count = statusMapper.insertSelective(orderStatus);
        if (count != 1) {
            log.error("[创建订单状态] 创建订单失败,orderId:{}",orderId);
            throw  new LyException(ExceptionEnum.CREATE_ORDER_ERROR);
        }
        //减库存
        List<CartDTO> carts = orderDTO.getCarts();
        goodsClient.decreaseStock(carts);
        return orderId;

    }
}

2.0.7 测试创建订单

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.订单

(1)点击支付后会跳转到支付页面

在这里插入图片描述

(2)实现上述所对应的接口(先实现查询订单)

(a)OrderController当中queryOrderById方法

在这里插入图片描述


    @GetMapping("{id}")
    public ResponseEntity<Order> queryOrderById(@PathVariable("id") Long id){
        return ResponseEntity.ok(orderService.queryOrderById(id));
    }

(b)完善OrderService当中对应的方法

在这里插入图片描述

在这里插入图片描述

    ORDER_NOT_FOUND(404,"订单不存在"),
    ORDER_DETAIL_NOT_FOUND(404,"订单详情不存在"),
    ORDER_STATUS_NOT_FOUND(404,"订单状态不存在"),

在这里插入图片描述

    public Order queryOrderById(Long id) {
        //查询订单
        Order order = orderMapper.selectByPrimaryKey(id);
        if(order == null){
            //订单不存在
            throw new  LyException(ExceptionEnum.ORDER_NOT_FOUND);
        }
        //查询订单详情
        OrderDetail detail = new OrderDetail();
        detail.setOrderId(id);
        List<OrderDetail> details = detailMapper.select(detail);
        if(CollectionUtils.isEmpty(details)){
            throw new LyException(ExceptionEnum.ORDER_DETAIL_NOT_FOUND);
        }
        order.setOrderDetails(details);
        //查询订单状态
        OrderStatus orderStatus = statusMapper.selectByPrimaryKey(id);
        if(orderStatus == null){
            throw new LyException(ExceptionEnum.ORDER_STATUS_NOT_FOUND);
        }
        order.setOrderStatus(orderStatus);

        return order;
    }

重新运行并测试
在这里插入图片描述
刷新支付页面

成功显示对应的数据
在这里插入图片描述

3.微信支付

(1)官网:

https://pay.weixin.qq.com/index.php

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
填写信息
在这里插入图片描述

(2)开发流程(实现后台代码)

模式二与模式一相比,流程更为简单,不依赖设置的回调支付URL。

商户后台系统先调用微信支付的统一下单接口,微信后台系统返回链接参数code_url;

商户后台系统将code_url值生成二维码图片,用户使用微信客户端扫码后发起支付。

注意:code_url有效期为2小时,过期后扫码不能再发起支付。

流程图:

在这里插入图片描述

  • 1、商户生成订单
  • 2、商户调用微信下单接口,获取预交易的链接
  • 3、商户将链接生成二维码图片,展示给用户;(码中包含,价格,收款方,订单号)
  • 4、用户支付并确认
  • 5、支付结果通知:
    • 微信异步通知商户支付结果,商户告知微信支付接收情况
    • 商户如果没有收到通知,可以调用接口,查询支付状态
  • 6、如果支付成功,发货,修改订单状态

在前面的业务中,我们已经完成了:

  • 1、生成订单

接下来,我们需要做的是:

  • 2、调用微信接口,生成链接。
  • 3、并且生成二维码图片
1)创建PayConfig

在这里插入图片描述

package com.leyou.order.config;

import com.github.wxpay.sdk.WXPayConfig;
import lombok.Data;

import java.io.InputStream;

@Data
public class PayConfig implements WXPayConfig {

    private String appID;  //公众账号ID

    private String mchID;  //商户号

    private String key;  //生成签名的密钥

    private int httpConnectTimeoutMs;   //连接超时时间

    private int httpReadTimeoutMs;  //读取超时时间

    private String notifyUrl;// 下单通知回调地址


    @Override
    public InputStream getCertStream() {
        return null;
    }
}

2)创建上述对应的配置文件

在这里插入图片描述

  pay:
    appId: wx8397f8696b538317
    mchId: 1473426802
    key: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
    connectTimeoutMs: 5000
    readTimeoutMs: 10000
    notifyUrl: http://k4v7yt.natappfree.cc/notify/wxpay
3)自定义WXPayConfiguration类封装PayConfig

在这里插入图片描述
在这里插入图片描述

package com.leyou.order.config;

import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConstants;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.Basic;

@Configuration
public class WXPayConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "ly.pay")
    public PayConfig payConfig(){
        return new PayConfig();
    }
    @Bean
    public WXPay wxPay(PayConfig payConfig){
        return new WXPay(payConfig, WXPayConstants.SignType.HMACSHA256);
    }


}

4)支付工具类PayHelper

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

package com.leyou.order.utils;

import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConstants;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.order.config.PayConfig;
import com.leyou.order.config.WXPayConfiguration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component //将其添加到Spring当中
@Slf4j
public class PayHelper {
    @Autowired
    private WXPay wxPay;
    @Autowired
    private PayConfig config;
    public String createPayUrl(Long orderId, Long totalPay, String description) {
        try {
            HashMap<String, String> data = new HashMap<>();
            //描述
            data.put("body", description);
            //订单号
            data.put("out_trade_no", orderId.toString());
            //货币(默认就是人民币)
            //data.put("fee_type", "CNY");
            //总金额
            data.put("total_fee", totalPay.toString());
            //调用微信支付的终端ip
            data.put("spbill_create_ip", "127.0.0.1");
            //回调地址
            data.put("notify_url", config.getNotifyUrl());
            //交易类型为扫码支付
            data.put("trade_type", "NATIVE");
            //利用wxPay工具,完成下单
            Map<String, String> result = this.wxPay.unifiedOrder(data);
            //判断通信的标识
            String return_code = result.get("return_code");
            if(WXPayConstants.FAIL.equals(return_code)){
                //通信失败
                log.error("[微信下单] 微信下单通信失败,失败原因:{}",result.get("return_msg"));
                throw new LyException(ExceptionEnum.WX_PAY_ORDER_FAIL);
            }
            //判断业务标识
            String result_code = result.get("result_code");
            if(WXPayConstants.FAIL.equals(result_code)){
                //通信失败
                log.error("[微信下单] 微信下单业务失败,错误码:{},错误原因:{}",result.get("err_code"),result.get("err_code_des"));
                throw new LyException(ExceptionEnum.WX_PAY_ORDER_FAIL);
            }
            //下单成功,获取支付连接
            String url = result.get("code_url");

            return url;
        } catch (Exception e) {
            log.error("【微信下单】创建预交易订单异常", e);
            return null;
        }
    }
}
5)编写OrderController的createOrderUrl方法

在这里插入图片描述


    /*
    创建支付链接
     */
    @GetMapping("/url/{id}")
    public ResponseEntity<String> createPayUrl(@PathVariable("id") Long orderId ){
        return ResponseEntity.ok(orderService.createPayUrl(orderId));

    }
6)完善OrderService

在这里插入图片描述

    ORDER_STATUS_ERROR(400,"订单状态异常"),

在这里插入图片描述
在这里插入图片描述


    @Autowired
    private PayHelper payHelper;

在这里插入图片描述


    public String createPayUrl(Long orderId) {
        //查询订单  //获取订单总金额
        Order order = queryOrderById(orderId);
        //判断订单状态
        if(order.getOrderStatus().getStatus() != OrderStatusEnum.UN_PAY.value()){
            //订单状态异常
            throw new LyException(ExceptionEnum.ORDER_STATUS_ERROR);
        }
        //获取总金额
        Long actualPay = order.getActualPay();
        actualPay  = 1L;
        //获取商品描述
        OrderDetail orderDetail = order.getOrderDetails().get(0);
        String desc = orderDetail.getTitle();
        return payHelper.createPayUrl(orderId,actualPay,desc);
    }

查询启动运行并测试

在这里插入图片描述
刷新支付页面
在这里插入图片描述
返回支付链接
在这里插入图片描述

(3)生成二维码

1)二维码生成插件qrious

qrious是一款基于HTML5 Canvas的纯JS二维码生成插件。

通过qrious.js可以快速生成各种二维码,你可以控制二维码的尺寸颜色,
还可以将生成的二维码进行Base64编码。
官网
https://github.com/neocotic/qrious

在js目录下引入qrcode.min.js
在这里插入图片描述
有pay.html当中已经写好了代码

刷新页面

在这里插入图片描述

(4)修改订单状态

1)内网穿透(外网访问本地服务)

内网穿透,也即 NAT 穿透,进行 NAT 穿透是为了使具有某一个特定源 IP 地址和源端口号的数据包不被 NAT 设备屏蔽而正确路由到内网主机。

下面就相互通信的主机在网络中与 NAT 设备的相对位置介绍内网穿透方法。

UDP 内网穿透的实质是利用路由器上的NAT 系统。

NAT 是一种将私有(保留)地址转化为合法IP地址的转换技术,它被广泛应用于各种类型 Internet 接入方式和各种类型的网络中。

NAT可以完成重用地址,并且对于内部的网络结构可以实现对外隐蔽。

免费的内网穿透工具

NATAPP

官网:https://natapp.cn/

在这里插入图片描述
登录注册

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
点击复制,即可得到 authtoken 这个authtoken便是您的隧道登录凭证.如这里得到的authtoken为9ab6b9040a624f40

下载客户端

在这里插入图片描述
在这里插入图片描述
解压
在这里插入图片描述

8.运行natapp

natapp支持两种运行方式

a) config.ini方式 (推荐)
  根据操作系统下载不同的config.ini文件到刚才下载的natapp.exe同级目录 详见
将第7步得到的authtoken填进去 (其他地方都不填),然后保存 
#将本文件放置于natapp同级目录程序将读取[default]段
#在命令行参数模式如natapp -authtoken=xxx等厢同参数将会覆盖掉此配置
[default]
authtoken=9ab6b9040a624f40
#对应一条隧道的authtoken
c1ienttoken=
#对应客户端的c1ienttoken,将会忽略authtoken,若无请留空,
1og=none
#1og且志文件,可以是none 代表不记录或者stdout.代表直接屏幕输出﹐默认为none
1oglevel=INFO
#日志等级DEBUG,INFO,WARNING,ERROR默认为 DEBUG
http_proxy=
#代理设置如http://110.123.10.10:3128

windows下,直接双击natapp.exe 即可.

 在Linux/Mac 下 需要先给执行权限

  chmod a+x natapp
 然后再运行


 ./natapp
 
 b) cmd -authtoken= 参数方式运行.
 windows ,点击开始->运行->命令行提示符 后进入 natapp.exe的目录
运行

      natapp -authtoken=9ab6b9040a624f40
linux ,同样给予可执行权限之后,运行



    ./natapp -authtoken=9ab6b9040a624f40

注意参数输入正确性,不要有多余的空格等!

9.运行成功,都可以得到如下界面:

在这里插入图片描述

Tunnel Status Online 代表链接成功
Version 当前客户端版本,如果有新版本,会有提示
Forwarding 当前穿透 网址 或者端口
Web Interface 是本地Web管理界面,可在隧道配置打开或关闭,仅用于web开发测试
Total Connections 总连接数
Avg Conn Time 0.00ms 这里不代表,不代表,不代表 延时,需要注意!

10.将natapp分配的网址(上图Forwarding ),鼠标选定然后复制下来(选定之后单击鼠标右键),在浏览器中访问,可以看到内网穿透成功了!

在这里插入图片描述

该网址 就是可以全球访问的网址,可以发给您的小伙伴试试 😃

2)在ly-order当中重新定义一个controller

在这里插入图片描述

在这里插入图片描述

package com.leyou.order.web;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("notify")
public class NotifyController {

}

3)修改MVC映射路径

在这里插入图片描述

package com.leyou.order.config;
import com.leyou.order.interceptors.UserInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableConfigurationProperties(JwtProperties.class)
public class MvcConfig implements WebMvcConfigurer {

    @Autowired
    private JwtProperties prop;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserInterceptor(prop))
                .addPathPatterns("/order/**")
                ;
        ;
    }
}

4)完善NotifyController并测试

在这里插入图片描述

package com.leyou.order.web;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("notify")
public class NotifyController {
    @GetMapping("{id}")
    public String hello(@PathVariable("id") Long id){
        return id.toString();
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5)完善订单功能

a、完善application.yml设置对应的路径

在这里插入图片描述

b、引入处理xml 的依赖

在这里插入图片描述

	    <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml </artifactId>
            <version>2.9.6</version>
        </dependency>
c、回调
  • NotifyController
    在这里插入图片描述
package com.leyou.order.web;

import com.leyou.order.service.OrderService;
import io.swagger.models.Xml;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("notify")
public class NotifyController {

    @Autowired
    private OrderService orderService;
    /*
    微信支付的成功回调
     */
    @PostMapping(value = "pay",produces = "application/xml")
    public Map<String,String> hello(@RequestBody Map<String,String> result){
        //处理回调
        orderService.handleNotify(result);
        //返回成功
        Map<String,String> msg = new HashMap<>();
        msg.put("return_code","SUCCESS");
        msg.put("return_msg","OK");
        return msg;
    }
}

  • OrderService
    在这里插入图片描述
    • 封装对应PayHelper部分代码为方法
      数据的校验
      在这里插入图片描述
      在这里插入图片描述
package com.leyou.order.utils;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConstants;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.order.config.PayConfig;
import com.leyou.order.config.WXPayConfiguration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;

@Component //将其添加到Spring当中
@Slf4j
public class PayHelper {
    @Autowired
    private WXPay wxPay;
    @Autowired
    private PayConfig config;

    public String createPayUrl(Long orderId, Long totalPay, String description) {
        try {
            HashMap<String, String> data = new HashMap<>();
            //描述
            data.put("body", description);
            //订单号
            data.put("out_trade_no", orderId.toString());
            //货币(默认就是人民币)
            //data.put("fee_type", "CNY");
            //总金额
            data.put("total_fee", totalPay.toString());
            //调用微信支付的终端ip
            data.put("spbill_create_ip", "127.0.0.1");
            //回调地址
            data.put("notify_url", config.getNotifyUrl());
            //交易类型为扫码支付
            data.put("trade_type", "NATIVE");
            //利用wxPay工具,完成下单
            Map<String, String> result = this.wxPay.unifiedOrder(data);
            //判断通信和业务标识
            isSuccess(result);
            //下单成功,获取支付连接
            String url = result.get("code_url");
            return url;
        } catch (Exception e) {
            log.error("【微信下单】创建预交易订单异常", e);
            return null;
        }
    }
    public void isSuccess(Map<String, String> result) {
        //判断通信的标识
        String return_code = result.get("return_code");
        if (WXPayConstants.FAIL.equals(return_code)) {
            //通信失败
            log.error("[微信下单] 微信下单通信失败,失败原因:{}", result.get("return_msg"));
            throw new LyException(ExceptionEnum.WX_PAY_ORDER_FAIL);
        }
        //判断业务标识
        String result_code = result.get("result_code");
        if (WXPayConstants.FAIL.equals(result_code)) {
            //通信失败
            log.error("[微信下单] 微信下单业务失败,错误码:{},错误原因:{}", result.get("err_code"), result.get("err_code_des"));
            throw new LyException(ExceptionEnum.WX_PAY_ORDER_FAIL);
        }
    }
}

签名校验
在这里插入图片描述

  INVALID_SIGN_ERROR(400,"无效的签名异常"),

在这里插入图片描述

    public void isValidSing(Map<String, String> data) {
        try {
            //重新生成签名
            String sign1 = WXPayUtil.generateSignature(data, config.getKey(), WXPayConstants.SignType.HMACSHA256);
            String sign2 = WXPayUtil.generateSignature(data, config.getKey(), WXPayConstants.SignType.MD5);
            //重新生成签名和传过来的签名做比较
            String sign = data.get("sign");
            if(!StringUtils.equals(sign,sign1) && !StringUtils.equals(sign,sign2)){
                //签名有误,抛出异常
                throw new LyException(ExceptionEnum.INVALID_SIGN_ERROR);
            }
        }catch (Exception  e){
            throw new LyException(ExceptionEnum.INVALID_SIGN_ERROR);
        }
    }

在这里插入图片描述

    INVALID_ORDER_PARAM(400,"订单参数异常"),
    UPDATE_ORDER_STATUS_ERROR(500,"更新订单状态失败"),
  • 继续完善OrderService当中的handleNotify

在这里插入图片描述


    public void handleNotify(Map<String, String> result) {
        // 数据校验
        payHelper.isSuccess(result);
        // 校验签名
        payHelper.isValidSing(result);
        // 检验金额
        String totalFeeStr = result.get("total_fee");
        String tradeNo = result.get("out_trade_no");
        if (StringUtils.isEmpty(totalFeeStr) || StringUtils.isEmpty(tradeNo)) {
            throw new LyException(ExceptionEnum.INVALID_ORDER_PARAM);
        }
        // 获取微信支付结果中的金额
        Long totalFee = Long.valueOf(totalFeeStr);

        //获取订单金额
        Long orderId = Long.valueOf(tradeNo);
        Order order = orderMapper.selectByPrimaryKey(orderId);
        // TODO 这里应该是判断是否等于 order.getActualPay()才对,因为前面支付测试是1分钱,所以这里也是一分钱
        if (totalFee != /*order.getActualPay()*/ 1L) {
            //微信支付金额与订单金额不符
            throw new LyException(ExceptionEnum.INVALID_ORDER_PARAM);
        }

       //修改订单状态
       //
        OrderStatus status = new OrderStatus();
        status.setStatus(OrderStatusEnum.PAYED.value());
        status.setOrderId(orderId);
        status.setPaymentTime(new Date());
        int count = statusMapper.updateByPrimaryKeySelective(status);
        if(count != 1){
            throw new LyException(ExceptionEnum.UPDATE_ORDER_STATUS_ERROR);
        }
    }


  • 运行测试
    在这里插入图片描述
    尝试支付
    在这里插入图片描述
    在这里插入图片描述

数据库当中也显示支付成功
在这里插入图片描述

6)查询订单状态跳转页面

a、分析页面数据

在这里插入图片描述

b、实现后台接口
OrderController当中的queryOrderState

在这里插入图片描述


    @GetMapping("/state/{id}")
    public ResponseEntity<Integer> queryOrderState(@PathVariable("id") Long orderId ){
        return ResponseEntity.ok(orderService.queryOrderState(orderId).getValue());
    }
OrderService当中queryOrderState

在这里插入图片描述

  • 创建新的状态枚举

在这里插入图片描述
在这里插入图片描述

package com.leyou.order.enums;

public enum  PayState {
    NOT_PAY(0),
    SUCCESS(1),
    FAIL(2)
    ;
    PayState(int value){
        this.value = value;
    }
    int value;
    public int getValue(){
        return value;
    }
}

  • 完善OrderService当中queryOrderState方法
    在这里插入图片描述
public PayState queryOrderState(Long orderId) {
        //查询订单状态
        OrderStatus orderStatus = statusMapper.selectByPrimaryKey(orderId);
        Integer status = orderStatus.getStatus();
        //判断是否支付
        if(status != OrderStatusEnum.UN_PAY.value()){
            //如已支付,就是真的支付
            return PayState.SUCCESS;
        }
        //如果是未支付,也可能是支付了但是延迟的,所以不确定,必须去微信查询支付状态
        payHelper.queryPayState(orderId);

        return null;
    }
  • 完善payHelper当中的queryPayState

在这里插入图片描述
在这里插入图片描述

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private OrderStatusMapper statusMapper;

在这里插入图片描述

public PayState queryPayState(Long orderId) {
        try {
            //组织请求参数
            HashMap<String, String> data = new HashMap<>();
            //订单号
            data.put("out_trade_no", orderId.toString());
            //货币(默认就是人民币)
            Map<String, String> result = wxPay.orderQuery(data);
            //校验通信状态
            isSuccess(result);
            //校验签名
            isValidSing(result);

            //校验金额
            String totalFeeStr = result.get("total_fee");
            String tradeNo = result.get("out_trade_no");
            if (StringUtils.isEmpty(totalFeeStr) || StringUtils.isEmpty(tradeNo)) {
                throw new LyException(ExceptionEnum.INVALID_ORDER_PARAM);
            }
            // 获取微信支付结果中的金额
            Long totalFee = Long.valueOf(totalFeeStr);

            //获取订单金额
            Order order = orderMapper.selectByPrimaryKey(orderId);
            // TODO 这里应该是判断是否等于 order.getActualPay()才对,因为前面支付测试是1分钱,所以这里也是一分钱
            if (totalFee != /*order.getActualPay()*/ 1L) {
                //微信支付金额与订单金额不符
                throw new LyException(ExceptionEnum.INVALID_ORDER_PARAM);
            }

            /*
            查询状态
             */
            String state = result.get("trade_state");

            if("SUCCESS".equals(state)){
                //支付成功
                //修改订单状态
                OrderStatus status = new OrderStatus();
                status.setStatus(OrderStatusEnum.PAYED.value());
                status.setOrderId(orderId);
                status.setPaymentTime(new Date());
                int count = statusMapper.updateByPrimaryKeySelective(status);
                if(count != 1){
                    throw new LyException(ExceptionEnum.UPDATE_ORDER_STATUS_ERROR);
                }
                return PayState.SUCCESS;
            }
            if("NOTPAY".equals(state) || "USERPAYING".equals(state)){
                return PayState.NOT_PAY;
            }
            return PayState.FAIL;
        }catch (Exception e){
            return PayState.NOT_PAY;
        }
    }
  • 刷新页面

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Logo

前往低代码交流专区

更多推荐