一.概述
之前章节开发的接口都是为了下订单接口的开发做了铺垫,下订单的接口内部将调用这些接口,是一个涉及到4-5个微服务的重量级接口.
二.下订单接口伪代码
可以看到该接口涉及的微服务有:server-user、server-goods、server-pay和server-order服务.
三.新建server-order微服务
1.创建maven工程.
2.server-order.pom中引入maven依赖.
<dependencies>
<dependency>
<groupId>com.ccm</groupId>
<artifactId>assembly-mysql</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
3.编写服务启动类.
package com.ccm.server.order;
import org.mybatis.spring.annotation.MapperScan;
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 org.springframework.context.annotation.ComponentScan;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* @Description server-order服务启动类
* @Author ccm
* @CreateTime 2020/8/14 9:47
*/
@EnableFeignClients
@EnableDiscoveryClient //注册中心客户端
@ComponentScan(basePackages = "com.ccm")
@EnableSwagger2
@SpringBootApplication //声明为一个启动类
@MapperScan(basePackages = "com.ccm.server.order.dao.mysql.mapper")
public class ServerOrderApplication {
public static void main(String[] args) {
SpringApplication.run(ServerOrderApplication.class,args);
}
}
4.编写bootstrap.yml配置文件.
server:
port: 7677 #服务端口
spring:
application:
name: server-order #服务名称
cloud:
nacos:
discovery:
server-addr: 47.96.131.185:8849
config:
server-addr: 47.96.131.185:8849 #nacos config配置中心ip和端口
file-extension: yaml #文件扩展名格式,针对于默认的{spring.application.name}-${profile}.${file-extension:properties}配置
enabled: true #开启或关闭配置中心
datasource:
username: root
password: Test2016@
url: jdbc:mysql://47.96.131.185:3306/order?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8&autoReconnect=true
type: com.alibaba.druid.pool.DruidDataSource
mybatis-plus:
typeAliasesPackage: com.ccm.server.order.dao.mysql.domain #数据库实体类包
mapper-locations: classpath:mappering/*.xml #xml文件扫描
#自定义配置
server-order:
serverNumber: 12
5.自定义配置的实体映射.
package com.ccm.server.order.constants;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @Description 自定义配置映射实体
* @Author ccm
* @CreateTime 2020/08/18 15:01
*/
@Slf4j
@Data
@Component
@ConfigurationProperties(prefix = "server-order")
public class ServerOrderProperties {
private String serverNumber;
@PostConstruct
public void init() {
log.info("ServerOrderProperties初始化完成......{}", JSONObject.toJSONString(this));
}
}
6.swagger配置.
package com.ccm.server.order.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import java.util.ArrayList;
import java.util.List;
/**
* @Description swagger配置
* @Author ccm
* @CreateTime 2020/08/14 17:38
*/
@Configuration
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
List<Parameter> pars = new ArrayList<Parameter>();
ParameterBuilder ticketPar = new ParameterBuilder();
ticketPar.name("ccm-token").description("必要参数(白名单接口无需传递)")
.modelRef(new ModelRef("string")).parameterType("header")
.required(false).build(); //header中的ticket参数非必填,传空也可以
pars.add(ticketPar.build());
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.ccm.server.order.controller")) //swagger接口扫描包
.paths(PathSelectors.any()).build().globalOperationParameters(pars);
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder().version("1.0.0")
.title("欢迎")
.description("光临")
.termsOfServiceUrl("www.baidu.com")
.build();
}
}
7.gateway网关加入server-order微服务的路由.
spring:
cloud:
gateway:
routes: #路由配置
- id: server-order #路由名称,不配默认为UUID
uri: lb://server-order #满足断言的路由到此服务
predicates: #为一个数组,每个规则为并且的关系
- Path=/api-order/** #断言表达式,如果args不写key的,会自动生成一个id,如下会生成一个xxx0的key,值为/foo/*
filters: #请求路由转发前执行的filter,为数组
- StripPrefix=1 #缩写,和name=StripPrefix,args,参数=1是一个意思,该过滤器为路由转发过滤去
- name: AuthFilter
四.业务代码编写
1.控制层
(1).OrderController
package com.ccm.server.order.controller;
import com.ccm.common.exception.result.ResultSet;
import com.ccm.server.order.controller.req.PayOrderReq;
import com.ccm.server.order.service.OrderService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.Size;
import java.util.List;
/**
* @Description 订单控制层
* @Author ccm
* @CreateTime 2020/08/14 14:46
*/
@Api(tags = "订单控制层")
@RestController
@RequestMapping(value = "order")
public class OrderController {
@Autowired
private OrderService orderService;
@ApiOperation(value = "下单")
@PostMapping
public ResultSet order(@Valid @Size(min = 1) @RequestBody List<PayOrderReq> payOrderReqList,
@ApiParam(hidden = true) @RequestHeader(name = "ccm-userId") Long userId) {
orderService.order(payOrderReqList,userId);
return ResultSet.success();
}
}
(2).PayOrderReq
package com.ccm.server.order.controller.req;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* @Description 下订单入参实体
* @Author ccm
* @CreateTime 2020/08/14 10:52
*/
@Data
@ApiModel(value = "下订单入参实体")
public class PayOrderReq {
@ApiModelProperty(value = "商品id")
private Long skuId;
@ApiModelProperty(value = "下单商品数量")
private Integer skuNumber;
}
2.业务层
(1).OrderService
package com.ccm.server.order.service;
import com.ccm.server.order.controller.req.PayOrderReq;
import java.util.List;
/**
* @Description 订单业务层
* @Author ccm
* @CreateTime 2020/08/14 10:10
*/
public interface OrderService {
/**
* @Description 下单
* @Author ccm
* @CreateTime 2020/8/20 13:54
* @Params [waitingPayReq, userId]
* @Return void
*/
void order(List<PayOrderReq> payOrderReqList, Long userId);
}
(2).OrderServiceImpl
package com.ccm.server.order.service.impl;
import com.alibaba.nacos.client.naming.utils.CollectionUtils;
import com.ccm.common.exception.CustomerException;
import com.ccm.common.exception.result.ResultSet;
import com.ccm.server.order.config.OrderIdGenerator;
import com.ccm.server.order.controller.req.PayOrderReq;
import com.ccm.server.order.dao.mysql.domain.OrderInfo;
import com.ccm.server.order.dao.mysql.domain.OrderSku;
import com.ccm.server.order.dao.mysql.mapper.OrderInfoMapper;
import com.ccm.server.order.dao.mysql.mapper.OrderSkuMapper;
import com.ccm.server.order.feign.ServerGoodsFeign;
import com.ccm.server.order.feign.ServerPayFeign;
import com.ccm.server.order.feign.req.ReduceStockReq;
import com.ccm.server.order.feign.vo.GoodsSkuVO;
import com.ccm.server.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Description 订单业务层实现类
* @Author ccm
* @CreateTime 2020/08/14 10:11
*/
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderSkuMapper orderSkuMapper;
@Autowired
private OrderInfoMapper orderInfoMapper;
@Autowired
private ServerGoodsFeign serverGoodsFeign;
@Autowired
private OrderIdGenerator orderIdGenerator;
@Autowired
private ServerPayFeign serverPayFeign;
@Override
@Transactional(rollbackFor = Exception.class)
public void order(List<PayOrderReq> payOrderReqList, Long userId) {
//生成订单号
String orderId = orderIdGenerator.nextOrderId();
//获取商品的详细信息
ResultSet<List<GoodsSkuVO>> resultSet = serverGoodsFeign.selectByIdList(payOrderReqList.stream()
.map(PayOrderReq::getSkuId)
.collect(Collectors.toList()));
List<GoodsSkuVO> goodsSkuVOList = ResultSet.getFeignData(resultSet);
if(CollectionUtils.isEmpty(goodsSkuVOList) || goodsSkuVOList.size()!=payOrderReqList.size()) {
throw new CustomerException("商品不存在");
}
//扣商品库存
List<ReduceStockReq> reduceStockReqList = payOrderReqList.stream()
.map(t -> new ReduceStockReq(t.getSkuId(),t.getSkuNumber()))
.collect(Collectors.toList());
ResultSet.getFeignData(serverGoodsFeign.reduceStock(reduceStockReqList));
//支付
BigDecimal totalMoney = new BigDecimal(0.0d);
for(PayOrderReq payOrderReq:payOrderReqList) {
for(GoodsSkuVO goodsSkuVO:goodsSkuVOList) {
if(payOrderReq.getSkuId().equals(goodsSkuVO.getId())) {
BigDecimal skuNumber = new BigDecimal(payOrderReq.getSkuNumber());
totalMoney = totalMoney.add(goodsSkuVO.getPrice().multiply(skuNumber));
break;
}
}
}
String flowingWaterId = ResultSet.getFeignData(serverPayFeign.pay(userId, totalMoney));
//主订单表插入数据
OrderInfo orderInfo = new OrderInfo();
orderInfo.setId(orderId);
orderInfo.setAmountMoney(totalMoney);
orderInfo.setStatus(1);
orderInfo.setUserId(userId);
orderInfo.setFlowingWaterId(flowingWaterId);
orderInfoMapper.insert(orderInfo);
//子订单表插入数据
ArrayList<OrderSku> orderSkuList = new ArrayList<>();
payOrderReqList.forEach(payOrderReq -> {
OrderSku orderSku = new OrderSku();
orderSku.setOrderId(orderId);
orderSku.setSkuNumber(payOrderReq.getSkuNumber());
orderSku.setSkuId(payOrderReq.getSkuId());
for(GoodsSkuVO goodsSkuVO:goodsSkuVOList) {
if(payOrderReq.getSkuId().equals(goodsSkuVO.getId())) {
orderSku.setSkuMoney(goodsSkuVO.getPrice());
break;
}
}
orderSkuList.add(orderSku);
});
orderSkuMapper.insertList(orderSkuList);
}
}
(3).OrderIdGenerator
package com.ccm.server.order.config;
import com.ccm.server.order.constants.ServerOrderProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
/**
* @Description 订单id生成器
* @Author ccm
* @CreateTime 2020/02/16 15:17
*/
@Component
public class OrderIdGenerator {
private Long lastTimeStamp = 0L; //上次生成订单id的时间戳
@Autowired
private ServerOrderProperties serverOrderProperties;
/**
* @Description 生成订单号
* @Author zhouzhiwu
* @CreateTime 2020/2/16 15:17
* @Params []
* @Return java.lang.String
*/
public String nextOrderId() {
Long timeStamp;
synchronized (this.getClass()) {
while(true) {
Long nowTimeStamp = System.currentTimeMillis();
if(!nowTimeStamp.equals(lastTimeStamp)) {
timeStamp = nowTimeStamp;
lastTimeStamp = nowTimeStamp;
break;
}
}
}
String timeStampStr = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date(timeStamp));
StringBuffer orderIdStringBuffer = new StringBuffer();
orderIdStringBuffer.append(timeStampStr)
.append(serverOrderProperties.getServerNumber())
.append(UUID.randomUUID().toString());
return orderIdStringBuffer.toString().substring(0,32);
}
}
3.调用外部服务的feign层.
(1).ServerGoodsFeign
package com.ccm.server.order.feign;
import com.ccm.common.exception.result.ResultSet;
import com.ccm.server.order.feign.req.ReduceStockReq;
import com.ccm.server.order.feign.vo.GoodsSkuVO;
import io.swagger.annotations.ApiOperation;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* @Description 调用server-goods服务feign层
* @Author ccm
* @CreateTime 2020/08/18 15:23
*/
@FeignClient(name = "server-goods")
public interface ServerGoodsFeign {
@ApiOperation(value = "减少库存入参")
@PutMapping(value = "goods/reduceStock")
ResultSet reduceStock(@RequestBody List<ReduceStockReq> reduceStockReqList);
@ApiOperation(value = "根据id获取sku")
@GetMapping(value = "goods/selectByIdList")
ResultSet<List<GoodsSkuVO>> selectByIdList(@RequestParam List<Long> idList);
}
(2).ServerPayFeign
package com.ccm.server.order.feign;
import com.ccm.common.exception.result.ResultSet;
import com.ccm.server.order.feign.req.ReduceStockReq;
import com.ccm.server.order.feign.vo.GoodsSkuVO;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @Description 调用server-pay服务feign层
* @Author ccm
* @CreateTime 2020/08/18 15:23
*/
@FeignClient(name = "server-pay")
public interface ServerPayFeign {
@ApiOperation(value = "支付")
@PostMapping(value = "pay/pay")
ResultSet<String> pay(@RequestHeader(name = "ccm-userId") Long userId,@RequestParam BigDecimal payMoney);
}
(3).ReduceStockReq
package com.ccm.server.order.feign.req;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @Description feign调用减少库存入参
* @Author ccm
* @CreateTime 2020/08/17 14:49
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
public class ReduceStockReq {
private Long skuId;
private Integer number;
}
(4).GoodsSkuVO
package com.ccm.server.order.feign.vo;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
/**
* @Description feign调用server-goods商品的结果视图
* @Author ccm
* @CreateTime 2020/08/19 10:48
*/
@Data
public class GoodsSkuVO {
private Long id;
private String name;
private BigDecimal price;
private Integer stock;
private Date createTime;
private Date updateTime;
}
3.持久层.
(1).OrderInfo
package com.ccm.server.order.dao.mysql.domain;
import lombok.Data;
import java.util.Date;
/**
* @Description order_info表实体类
* @Author ccm
* @CreateTime 2020/08/17 10:44
*/
@Data
public class OrderInfo {
private String id;
private BigDecimal amountMoney;
private Integer status;
private Long userId;
private String flowingWaterId;
private Date updateTime;
private Date createTime;
}
(2).OrderSku
package com.ccm.server.order.dao.mysql.domain;
import lombok.Data;
import java.util.Date;
/**
* @Description order_sku表实体类
* @Author ccm
* @CreateTime 2020/08/17 10:46
*/
@Data
public class OrderSku {
private Long id;
private Long skuId;
private Integer skuNumber;
private BigDecimal skuMoney;
private String orderId;
private Date createTime;
}
(3).OrderInfoMapper
package com.ccm.server.order.dao.mysql.mapper;
import com.ccm.server.order.dao.mysql.domain.OrderInfo;
/**
* @Description order_info表持久层
* @Author ccm
* @CreateTime 2020/08/17 10:49
*/
public interface OrderInfoMapper {
int insert(OrderInfo orderInfo);
}
(4).OrderSkuMapper
package com.ccm.server.order.dao.mysql.mapper;
import com.ccm.server.order.dao.mysql.domain.OrderSku;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @Description order_sku表持久层
* @Author ccm
* @CreateTime 2020/08/17 10:49
*/
public interface OrderSkuMapper {
int insertList(@Param("list") List<OrderSku> orderSkuList);
}
(5).OrderInfoMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.ccm.server.order.dao.mysql.mapper.OrderInfoMapper" >
<insert id="insert" parameterType="com.ccm.server.order.dao.mysql.domain.OrderInfo" >
insert into order_info
<trim prefix="(" suffix=")" suffixOverrides="," >
<if test="id != null and id != ''" >
id,
</if>
<if test="amountMoney != null and amountMoney != ''" >
amount_money,
</if>
<if test="status != null and status != ''" >
`status`,
</if>
<if test="userId != null and userId != ''" >
user_id,
</if>
<if test="flowingWaterId != null and flowingWaterId != ''" >
flowing_water_id,
</if>
update_time,
create_time,
</trim>
<trim prefix="values (" suffix=")" suffixOverrides="," >
<if test="id != null and id != ''" >
#{id},
</if>
<if test="amountMoney != null and amountMoney != ''" >
#{amountMoney},
</if>
<if test="status != null and status != ''" >
#{status},
</if>
<if test="userId != null and userId != ''" >
#{userId},
</if>
<if test="flowingWaterId != null and flowingWaterId != ''" >
#{flowingWaterId},
</if>
now(),
now(),
</trim>
</insert>
</mapper>
(6).OrderSkuMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.ccm.server.order.dao.mysql.mapper.OrderSkuMapper" >
<insert id="insertList" parameterType="java.util.List" >
insert into order_sku
(
sku_id,
sku_number,
sku_money,
order_id,
create_time
)
values
<foreach collection="list" item="item" index="index" separator=",">
(
#{item.skuId},
#{item.skuNumber},
#{item.skuMoney},
#{item.orderId},
now()
)
</foreach>
</insert>
</mapper>
(7).order.order_info表结构
(8).order.order_sku表结构
五.测试
1.数据库基础数据准备
(1).goods.goods_sku增加两个商品
(2).user.user_info为ccm用户的账户余额到10万
2.启动gateway、server-user、server-goods、server-pay和server-order服务.
3.打开gateway的swagger界面.
4.调用下单接口,买一台苹果11和一台华为P30.
5.查看数据库的数据变化.
(1).user.user_info表,账户余额被扣除
(2).pay.pay_flowing_water支付流水表,生成了流水记录
(3).goods.goods_sku商品表,库存减少了
(4).order.order_info主订单表,有了一笔主订单
(5).order.order_sku子订单表,有了两笔子订单
至此,完事!
您的点赞、收藏、转发和关注是我持续创作的动力!
源码地址:https://gitee.com/chouchimoo/ccm-mall.git(本章节代码分支:zj-34)