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-servicedatasource:url: jdbc:mysql://127.0.0.1:3306/yun6username: rootpassword: rootdriver-class-name: com.mysql.jdbc.Driverjackson:default-property-inclusion: non_null
eureka:client:service-url:defaultZone: http://127.0.0.1:10086/eurekaregistry-fetch-interval-seconds: 5instance:ip-address: 127.0.0.1prefer-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;@PostConstructpublic 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;}@Overridepublic boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception {//获取CookieString token = CookieUtils.getCookieValue(request, prop.getCookieName());try {//解析tokenUserInfo user = JwtUtils.getUserInfo(prop.getPublicKey(), token);//传递Usertl.set(user);//放行return true;} catch (Exception e) {log.error("[购物车服务]解析用户身份失败", e);return false;}}@Overridepublic 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 {@Autowiredprivate JwtProperties prop;@Overridepublic 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 {@Idprivate Long orderId;// idprivate Long totalPay;// 总金额private Long actualPay;// 实付金额private Integer paymentType; // 支付类型,1、在线支付,2、货到付款private String promotionIds; // 参与促销活动的idprivate Long postFee = 0L;// 邮费private Date createTime;// 创建时间private String shippingName;// 物流名称private String shippingCode;// 物流单号private Long userId;// 用户idprivate 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; // 邮政编码,如:310001private Integer invoiceType = 0;// 发票类型,0无发票,1普通发票,2电子发票,3增值税发票private Integer sourceType = 1;// 订单来源 1:app端,2:pc端,3:M端,4:微信端,5:手机qq端@Transientprivate OrderStatus orderStatus;@Transientprivate 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;// 订单idprivate Long skuId;// 商品idprivate 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 {@Idprivate 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;//商品skuIdprivate Integer num;//购买数量}


@Data
@AllArgsConstructor
@NoArgsConstructor
public class OrderDTO {//数据传输对象@NonNull  //@NonNull校验不能为空private Long addressId;@NonNullprivate Integer paymentType;@NonNullprivate 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 {/*创建订单*/@PostMappingpublic 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 {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate OrderDetailMapper detailMapper;@Autowiredprivate 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 : 1dataCenterId : 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 {@Beanpublic 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


@Transactionalpublic 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 {@Autowiredprivate GoodsService goodsService;@Testpublic 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
@Slf4jpublic class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate OrderDetailMapper detailMapper;@Autowiredprivate OrderStatusMapper statusMapper;@Autowiredprivate IdWorker idWorker;@Autowiredprivate GoodsClient goodsClient;@Transactionalpublic 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,值是numMap<Long, Integer> numMap = orderDTO.getCarts().stream().collect(Collectors.toMap(CartDTO::getSkuId, CartDTO::getNum));//获取所有的sku的idSet<Long> ids = numMap.keySet();//根据id查询skuList<Sku> skus = goodsClient.querySkuBySpuId(new ArrayList<>(ids));//准备orderDetailList<OrderDetail> details = new ArrayList<>();long totalPay = 0L;for (Sku sku : skus) {//计算商品总价totalPay += sku.getPrice() * numMap.get(sku.getId());//封装OrderDetailOrderDetail 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;  //公众账号IDprivate String mchID;  //商户号private String key;  //生成签名的密钥private int httpConnectTimeoutMs;   //连接超时时间private int httpReadTimeoutMs;  //读取超时时间private String notifyUrl;// 下单通知回调地址@Overridepublic InputStream getCertStream() {return null;}
}
2)创建上述对应的配置文件

  pay:appId: wx8397f8696b538317mchId: 1473426802key: T6m9iK73b0kn9g5v426MKfHQH7X8rKwbconnectTimeoutMs: 5000readTimeoutMs: 10000notifyUrl: 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();}@Beanpublic 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 {@Autowiredprivate WXPay wxPay;@Autowiredprivate 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());//调用微信支付的终端ipdata.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,"订单状态异常"),


@Autowiredprivate 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然后再运行./natappb) 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 ),鼠标选定然后复制下来(选定之后单击鼠标右键),在浏览器中访问,可以看到内网穿透成功了!

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

Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)二十二(下单和微信支付)相关推荐

  1. Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)十六(商品排序,Thymeleaf快速入门,商品详情页的展示)

    Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)十六(商品详情页的展示) 一.商品排序 1.完善页面信息 这是用来做排序的,默认按照综合排序 ...

  2. Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)十四(Spring Data Elasticsearch,将数据添加到索引库)

    Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)十四(Spring Data Elasticsearch,将数据添加到索引库) 一.创建El ...

  3. 基于Vue和Django搭建前后端分离项目

    前言 最近公司要做一个系统,需要前后端分离,后端使用Django,前端使用Vue,因为之前主要做的是后端开发,前端只会写一写简单的,Vue只会绑定数据,至于Vue怎么与Django结合还是第一次接触, ...

  4. 基于 SpringBoot + Vue 的在线课堂前后端分离项目

    开发时间:2022.10.17 - 2022.11.04 开源项目: 服务端:atguigu-course-backend 后台管理系统:atguigu-course-frontend 移动端微信公众 ...

  5. 基于SpringBoot,vue,node的前后端分离小米商城的设计与实现

    1,项目介绍 注:本项目拥有两个版本,下单前请及时告知自己所需的版本(两个版本只有技术栈不同): 版本一:spring boot+vue+MySQL 版本二:node.js+vue+MySQL 项目简 ...

  6. Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)十(商品的规格类型以及参数管理)

    一.商品规格数据结构 商品中都有属性,不同商品,属性往往不同,这一部分数据很重要,我们一起来看看: 1.规格属性内容 (1) 我们看下京东中商品的规格属性︰ -款华为手机的属性: (2)横表和竖表 值 ...

  7. python前后端分离项目部署_nginx+uwsgi+supervisor部署django前后端分离项目

    以下内容为原创,转载请注明出处! 先前一直用的apache部署django项目,查看链接地址:https://www.520pf.cn/article/22.html .这次帮同事用nginx部署服务 ...

  8. 小白快速上手前后端分离项目开发教程(springboot+vue)

    本文基于springboot+vue,实现一个前后端分离项目的实操.通俗易懂,保证一学就会.同时能帮助大家更好的理解,什么是前后端分离开发?这个开发跟之前使用jsp或者模板引擎开发有什么不同. 一.为 ...

  9. 在Docker 上完成对Springboot+Mysql+Redis的前后端分离项目的部署(全流程,全截图)

    本文章全部阅读大约2小时,包含一个完整的springboot + vue +mysql+redis前后端分离项目的部署在docker上的全流程,比较复杂,请做好心理准备,遇到问题可留言或则私信 目录 ...

最新文章

  1. php实现cookie加密解密
  2. 极客时间《玩转Git三剑客》之GItHub剑客
  3. 一本书让你懂得人生的价值
  4. JavaScript 正则表达式(RegExp对象、属性、方法、String支持)
  5. python语言的单行注释以井号开头_python-注释
  6. 【redis】redisDesktopManager之redis可视化客户端 界面介绍
  7. servlet容器开发要点
  8. 你真的了解JavaScript的Promise吗?
  9. numpy与tensorflow中的广播(broadcast)机制
  10. Modscan和Modsim 两种Modbus调试工具使用说明
  11. 【obs】27:deps 构建说明及studio的vs2019构建及裁剪
  12. 【矩阵论】矩阵的广义逆
  13. 解方程计算器,一款数学神器APP,有需要的自己收藏!
  14. 基于时空双向注意力的乘车需求预测模型——以新冠肺炎期间北京市为例
  15. BSGSexBSGS(让你轻松理解和掌握)
  16. python真的有用吗-Python真的没有用吗?
  17. programming notes - 在线学习资源
  18. 2022年,开源社最亮的星
  19. 12月18日蓝桥杯STEMA比赛题目(C++)
  20. 抓取网页工具querylist的使用简介

热门文章

  1. php mysql简单通讯录_PHP 制作通讯录(一)
  2. UE4 3dsmax 一个电脑屏幕模型的demo
  3. 【插件】打造属于自己的cnpm/npm安装,生成自定义项目架构
  4. 以后想干私活?我建议你收藏一下这几个项目!
  5. 解决libpangoft2-1.0.so.0:对‘pango_font_get_hb_font‘未定义的引用
  6. E70_433半双工无线模组,伪全双工方案
  7. 反射型xss钓鱼盗取用户cookie
  8. 智能电视TV开发---直播视频客户端结构设计和实现
  9. 【读书笔记】《为什么精英都是时间控》总结整理 - 干货满满的时间管理书籍
  10. ECSHOP和SHOPEX快递单号查询国际EMS插件V8.6专版