[TOC]
1 订单结页
1.1 收件地址分析
用户从购物车页面点击结算,跳转到订单结算页,结算页需要加载用户对应的收件地址,如下图:
表结构分析:
CREATE TABLE `address_` (
`id_` int(11) NOT NULL AUTO_INCREMENT,
`username_` varchar(50) DEFAULT NULL COMMENT '用户名',
`province_` varchar(20) DEFAULT NULL COMMENT '省',
`city_` varchar(20) DEFAULT NULL COMMENT '市',
`area_` varchar(20) DEFAULT NULL COMMENT '县/区',
`phone_` varchar(20) DEFAULT NULL COMMENT '电话',
`address_` varchar(200) DEFAULT NULL COMMENT '详细地址',
`contact_` varchar(50) DEFAULT NULL COMMENT '联系人',
`is_default_` varchar(1) DEFAULT NULL COMMENT '是否是默认 1默认 0否',
`alias_` varchar(50) DEFAULT NULL COMMENT '别名',
PRIMARY KEY (`id_`)
) ENGINE=InnoDB AUTO_INCREMENT=66 DEFAULT CHARSET=utf8;
我们可以根据用户登录名去address_表中查询对应的数据。
1.2 实现用户收件地址查询
1.2.1 代码实现
实体类
在用户微服务工程中增加Address
legou-security/legou-security-instance/src/main/java/com/lxs/legou/security/po/Address.java
package com.lxs.legou.security.po;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.lxs.legou.core.po.BaseEntity;
import lombok.Data;
@Data
@TableName("address_")
public class Address extends BaseEntity {
@TableField("username_")
private String username;//用户名
@TableField("province_")
private String provinceid;//省
@TableField("city_")
private String cityid;//市
@TableField("area_")
private String areaid;//县/区
@TableField("phone_")
private String phone;//电话
@TableField("address_")
private String address;//详细地址
@TableField("contact_")
private String contact;//联系人
@TableField("is_default_")
private String isDefault;//是否是默认 1默认 0否
@TableField("alias_")
private String alias;//别名
}
dao
legou-security/legou-security-service/src/main/java/com/lxs/legou/security/dao/AddressDao.java
package com.lxs.legou.security.dao;
import com.lxs.legou.core.dao.ICrudDao;
import com.lxs.legou.security.po.Address;
public interface AddressDao extends ICrudDao<Address> {
}
service
legou-security/legou-security-service/src/main/java/com/lxs/legou/security/service/IAddressService.java
package com.lxs.legou.security.service;
import com.lxs.legou.core.service.ICrudService;
import com.lxs.legou.security.po.Address;
public interface IAddressService extends ICrudService<Address> {
}
legou-security/legou-security-service/src/main/java/com/lxs/legou/security/service/impl/AddressServiceImpl.java
package com.lxs.legou.security.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.lxs.legou.core.service.impl.CrudServiceImpl;
import com.lxs.legou.security.po.Address;
import com.lxs.legou.security.service.IAddressService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class AddressServiceImpl extends CrudServiceImpl<Address> implements IAddressService {
@Override
public List<Address> list(Address entity) {
//根据用户名查询用户收货地址
QueryWrapper<Address> queryWrapper = Wrappers.<Address>query();
if (StringUtils.isNotEmpty(entity.getUsername())) {
queryWrapper.eq("username_", entity.getUsername());
}
return getBaseMapper().selectList(queryWrapper);
}
}
legou-security/legou-security-service/src/main/java/com/lxs/legou/security/controller/AddressController.java
package com.lxs.legou.security.controller;
import com.lxs.legou.core.controller.BaseController;
import com.lxs.legou.security.config.TokenDecode;
import com.lxs.legou.security.po.Address;
import com.lxs.legou.security.service.IAddressService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping(value = "/address")
public class AddressController extends BaseController<IAddressService, Address> {
@Autowired
private TokenDecode tokenDecode;
@Override
@ApiOperation(value="查询", notes="根据实体条件查询")
@RequestMapping(value = "/list", method = {RequestMethod.POST, RequestMethod.GET})
public List<Address> list(Address entity) {
Map<String, String> user = null;
try {
user = tokenDecode.getUserInfo();
} catch (IOException e) {
e.printStackTrace();
}
String username = user.get("user_name");
entity.setUsername(username);
return service.list(entity);
}
}
1.2.2 测试
使用postman携带令牌访问
1.2.3 运送清单
运送清单其实就是购物车列表,直接查询之前的购物车列表即可,这里不做说明了。
2 下单
2.1 业务分析
点击结算页提交订单的时候,会立即创建订单数据,创建订单数据会将数据存入到2张表中,分别是订单表和订单明细表,此处还需要修改商品对应的库存数量,改变用户的积分,然后删除应该的购物车数据
订单表结构如下:
CREATE TABLE `order_` (
`id` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '订单id',
`total_num` int(11) DEFAULT NULL COMMENT '数量合计',
`total_money` int(11) DEFAULT NULL COMMENT '金额合计',
`pre_money` int(11) DEFAULT NULL COMMENT '优惠金额',
`post_fee` int(11) DEFAULT NULL COMMENT '邮费',
`pay_money` int(11) DEFAULT NULL COMMENT '实付金额',
`pay_type` varchar(1) COLLATE utf8_bin DEFAULT NULL COMMENT '支付类型,1、在线支付、0 货到付款',
`create_time` datetime DEFAULT NULL COMMENT '订单创建时间',
`update_time` datetime DEFAULT NULL COMMENT '订单更新时间',
`pay_time` datetime DEFAULT NULL COMMENT '付款时间',
`consign_time` datetime DEFAULT NULL COMMENT '发货时间',
`end_time` datetime DEFAULT NULL COMMENT '交易完成时间',
`close_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 '物流单号',
`username` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '用户名称',
`buyer_message` varchar(1000) COLLATE utf8_bin DEFAULT NULL COMMENT '买家留言',
`buyer_rate` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '是否评价',
`receiver_contact` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '收货人',
`receiver_mobile` varchar(12) COLLATE utf8_bin DEFAULT NULL COMMENT '收货人手机',
`receiver_address` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '收货人地址',
`source_type` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '订单来源:1:web,2:app,3:微信公众号,4:微信小程序 5 H5手机页面',
`transaction_id` varchar(30) COLLATE utf8_bin DEFAULT NULL COMMENT '交易流水号',
`order_status` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '订单状态 ',
`pay_status` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '支付状态 0:未支付 1:已支付',
`consign_status` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '发货状态 0:未发货 1:已发货 2:已送达',
`is_delete` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
KEY `create_time` (`create_time`) USING BTREE,
KEY `status` (`order_status`) USING BTREE,
KEY `payment_type` (`pay_type`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=DYNAMIC;
订单明细表结构如下:
CREATE TABLE `order_item_` (
`id` varchar(200) COLLATE utf8_bin NOT NULL COMMENT 'ID',
`category_id1` int(11) DEFAULT NULL COMMENT '1级分类',
`category_id2` int(11) DEFAULT NULL COMMENT '2级分类',
`category_id3` int(11) DEFAULT NULL COMMENT '3级分类',
`spu_id` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT 'SPU_ID',
`sku_id` varchar(200) COLLATE utf8_bin NOT NULL COMMENT 'SKU_ID',
`order_id` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '订单ID',
`name` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '商品名称',
`price` int(20) DEFAULT NULL COMMENT '单价',
`num` int(10) DEFAULT NULL COMMENT '数量',
`money` int(20) DEFAULT NULL COMMENT '总金额',
`pay_money` int(11) DEFAULT NULL COMMENT '实付金额',
`image` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '图片地址',
`weight` int(11) DEFAULT NULL COMMENT '重量',
`post_fee` int(11) DEFAULT NULL COMMENT '运费',
`is_return` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '是否退货',
PRIMARY KEY (`id`) USING BTREE,
KEY `item_id` (`sku_id`) USING BTREE,
KEY `order_id` (`order_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=DYNAMIC;
2.2 下单实现
下单的时候,先添加订单往order_表中增加数据,再添加订单明细,往orderitem表中增加数据。
2.2.1 生成订单号
分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。
有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。
而twitter的SnowFlake解决了这种需求,最初Twitter把存储系统从MySQL迁移到Cassandra,因为Cassandra没有顺序ID生成机制,所以开发了这样一套全局唯一ID生成服务。
所有生成的id按时间趋势递增
分布式系统内不会产生重复id(因为有datacenterId和workerId来做区分)
工具类legou-common/src/main/java/com/lxs/legou/common/utils/IdWorker.java
package com.lxs.legou.common.utils;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;
/**
* <p>名称:IdWorker.java</p>
* <p>描述:分布式自增长ID</p>
* <pre>
* Twitter的 Snowflake JAVA实现方案
* </pre>
* 核心代码为其IdWorker这个类实现,其原理结构如下,我分别用一个0表示一位,用—分割开部分的作用:
* 1||0---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000
* 在上面的字符串中,第一位为未使用(实际上也可作为long的符号位),接下来的41位为毫秒级时间,
* 然后5位datacenter标识位,5位机器ID(并不算标识符,实际是为线程标识),
* 然后12位该毫秒内的当前毫秒内的计数,加起来刚好64位,为一个Long型。
* 这样的好处是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和机器ID作区分),
* 并且效率较高,经测试,snowflake每秒能够产生26万ID左右,完全满足需要。
* <p>
* 64位ID (42(毫秒)+5(机器ID)+5(业务编码)+12(重复累加))
*
* @author Polim
*/
public class IdWorker {
// 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
private final static long twepoch = 1288834974657L;
// 机器标识位数
private final static long workerIdBits = 5L;
// 数据中心标识位数
private final static long datacenterIdBits = 5L;
// 机器ID最大值
private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
// 数据中心ID最大值
private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
// 毫秒内自增位
private final static long sequenceBits = 12L;
// 机器ID偏左移12位
private final static long workerIdShift = sequenceBits;
// 数据中心ID左移17位
private final static long datacenterIdShift = sequenceBits + workerIdBits;
// 时间毫秒左移22位
private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
/* 上次生产id时间戳 */
private static long lastTimestamp = -1L;
// 0,并发控制
private long sequence = 0L;
private final long workerId;
// 数据标识id部分
private final long datacenterId;
public IdWorker(){
this.datacenterId = getDatacenterId(maxDatacenterId);
this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
}
/**
* @param workerId
* 工作机器ID
* @param datacenterId
* 序列号
*/
public IdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/**
* 获取下一个ID
*
* @return
*/
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
if (lastTimestamp == timestamp) {
// 当前毫秒内,则+1
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 当前毫秒内计数满了,则等待下一秒
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
// ID偏移组合生成最终的ID,并返回ID
long nextId = ((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift) | sequence;
return nextId;
}
private long tilNextMillis(final long lastTimestamp) {
long timestamp = this.timeGen();
while (timestamp <= lastTimestamp) {
timestamp = this.timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
/**
* <p>
* 获取 maxWorkerId
* </p>
*/
protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
StringBuffer mpid = new StringBuffer();
mpid.append(datacenterId);
String name = ManagementFactory.getRuntimeMXBean().getName();
if (!name.isEmpty()) {
/*
* GET jvmPid
*/
mpid.append(name.split("@")[0]);
}
/*
* MAC + PID 的 hashcode 获取16个低位
*/
return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
}
/**
* <p>
* 数据标识id部分
* </p>
*/
protected static long getDatacenterId(long maxDatacenterId) {
long id = 0L;
try {
InetAddress ip = InetAddress.getLocalHost();
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
if (network == null) {
id = 1L;
} else {
byte[] mac = network.getHardwareAddress();
id = ((0x000000FF & (long) mac[mac.length - 1])
| (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
id = id % (maxDatacenterId + 1);
}
} catch (Exception e) {
System.out.println(" getDatacenterId: " + e.getMessage());
}
return id;
}
}
修改实体类,主键策略不能使用自增,而是使用手动赋值这种主键策略
@Data
@JsonIgnoreProperties(value = {"handler"})
public abstract class BaseEntity implements Serializable {
/**
* 实体编号(唯一标识)
*/
@TableId(value = "id_", type = IdType.AUTO)
protected Long id;
}
2.2.2 Dao
legou-order/legou-order-service/src/main/java/com/lxs/legou/order/dao/OrderDao.java
package com.lxs.legou.order.dao;
import com.lxs.legou.core.dao.ICrudDao;
import com.lxs.legou.order.po.Order;
public interface OrderDao extends ICrudDao<Order> {
}
mybatis\order\OrderDao.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.lxs.legou.order.dao.OrderDao">
<select id="selectByPage" resultType="Order">
select
*
from
order_
<where>
<if test="username != null and username != ''">
username_ = #{username}
</if>
</where>
</select>
</mapper>
com.lxs.legou.order.dao.OrderItemDao
package com.lxs.legou.order.dao;
import com.lxs.legou.core.dao.ICrudDao;
import com.lxs.legou.order.po.OrderItem;
public interface OrderItemDao extends ICrudDao<OrderItem> {
}
2.2.3 Service
com.lxs.legou.order.service.OrderService
package com.lxs.legou.order.service;
import com.lxs.legou.core.service.ICrudService;
import com.lxs.legou.order.po.Order;
public interface OrderService extends ICrudService<Order> {
/**
* 增加订单
* @param order
*/
public void add(Order order);
}
com.lxs.legou.order.service.impl.OrderServiceImpl
package com.lxs.legou.order.service.impl;
import com.lxs.legou.order.client.SkuClient;
import com.lxs.legou.order.client.UserClient;
import com.lxs.legou.order.dao.OrderItemDao;
import com.lxs.legou.order.service.OrderService;
import com.lxs.legou.common.utils.IdWorker;
import com.lxs.legou.core.service.impl.CrudServiceImpl;
import com.lxs.legou.order.po.Order;
import com.lxs.legou.order.po.OrderItem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
@Service
public class OrderServiceImpl extends CrudServiceImpl<Order> implements OrderService {
@Autowired
private IdWorker idWorker;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private OrderItemDao orderItemDao;
@Autowired
private UserClient userClient;
@Autowired
private SkuClient skuClient;
@Override
public void add(Order order) {
//1.添加订单表的数据
order.setId(idWorker.nextId());
List<OrderItem> values = redisTemplate.boundHashOps("Cart_" + order.getUsername()).values();
Long totalNum = 0l;//购买总数量
Long totalMoney = 0l;//购买的总金额
for (OrderItem orderItem : values) {
totalNum += orderItem.getNum();//购买的数量
totalMoney += orderItem.getPayMoney();//金额
//2.添加订单选项表的数据
orderItem.setId(idWorker.nextId());//订单选项的iD
orderItem.setOrderId(order.getId());//订单的iD
orderItem.setIsReturn("0");//未退货
orderItemDao.insert(orderItem);
//3. 减少库存 调用goods 微服务的 feign 减少库存
}
order.setTotalNum(totalNum);//设置总数量
order.setTotalMoney(totalMoney);//设置总金额
order.setPayMoney(totalMoney);//设置实付金额
order.setCreateTime(new Date());
order.setUpdateTime(order.getCreateTime());
order.setOrderStatus("0");//0:未完成
order.setPayStatus("0");//未支付
order.setConsignStatus("0");//未发货
order.setIsDelete("0");//未删除
getBaseMapper().insert(order);
//4.增加积分 调用用户微服务的userfeign 增加积分
//5.清空当前的用户的redis中的购物车
redisTemplate.delete("Cart_" + order.getUsername());
}
}
2.2.4 Controller
com.lxs.legou.order.controller.OrderController
package com.lxs.legou.order.controller;
import com.lxs.legou.order.config.TokenDecode;
import com.lxs.legou.order.service.OrderService;
import com.lxs.legou.core.controller.BaseController;
import com.lxs.legou.order.po.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@RestController
@RequestMapping("/order")
public class OrderController extends BaseController<OrderService, Order> {
@Autowired
private TokenDecode tokenDecode;
/**
* 添加订单
* @param order
* @return
* @throws IOException
*/
@PostMapping("/add")
public ResponseEntity add(@RequestBody Order order) throws IOException {
order.setUsername(tokenDecode.getUserInfo().get("user_name"));
service.add(order);
return ResponseEntity.ok("添加成功");
}
}
2.2.5 测试
2.3 库存变更
2.3.1 业务分析
上面操作只实现了下单操作,但对应的库存还没跟着一起减少,我们在下单之后,应该调用商品微服务,将下单的商品库存减少,销量增加。每次订单微服务只需要将用户名传到商品微服务,商品微服务通过用户名到Redis中查询对应的购物车数据,然后执行库存减少,库存减少需要控制当前商品库存>=销售数量。
如何控制库存数量>=销售数量呢?其实可以通过SQL语句实现,每次减少数量的时候,加个条件判断。
where num>=#{num}
即可。
2.3.2 代码实现
Dao
修改legou-item/legou-item-service/src/main/java/com/lxs/legou/item/dao/SkuDao.java
@Update(value="update sku_ set stock_ = stock_ - #{num} where id_ =#{skuId} and stock_ >= #{num}")
public int decrCount(@Param("num") Integer num, @Param("skuId") Long skuId);
service
修改legou-item/legou-item-service/src/main/java/com/lxs/legou/item/service/ISkuService.java
/**
* 减库存
* @param num
* @param skuId
*/
public void decrCount(Integer num, Long skuId);
修改legou-item/legou-item-service/src/main/java/com/lxs/legou/item/service/impl/SkuServiceImpl.java
@Override
public void decrCount(Integer num, Long skuId) {
((SkuDao) getBaseMapper()).decrCount(num, skuId);
}
controller
修改legou-item/legou-item-service/src/main/java/com/lxs/legou/item/controller/SkuController.java
/**
* 减库存
* @param num
* @param skuId
*/
@PostMapping(value = "/decr-count")
public void decrCount(@RequestParam("num") Integer num, @RequestParam("skuId") Long skuId) {
service.decrCount(num, skuId);
}
注意:通过Feign调用Controller如果有多个参数,必须写@RequestParam,否则抛出下面异常
Caused by: java.lang.IllegalStateException: Method has too many Body parameters: public abstract void com.lxs.legou.item.api.SkuApi.decrCount(java.lang.Integer,java.lang.Long)
Feign Client
legou-item/legou-item-instance/src/main/java/com/lxs/legou/item/api/SkuApi.java
package com.lxs.legou.item.api;
import com.lxs.legou.item.po.Sku;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RequestMapping(value = "/sku")
public interface SkuApi {
@ApiOperation(value="查询spu对应的sku", notes="根据spuId查询sku集合")
@GetMapping("/select-skus-by-spuid/{id}")
public List<Sku> selectSkusBySpuId(@PathVariable("id") Long spuId);
@ApiOperation(value="加载", notes="根据ID加载")
@GetMapping("/edit/{id}")
public Sku edit(@PathVariable Long id);
@PostMapping(value = "/decr-count")
public void decrCount(@RequestParam("num") Integer num, @RequestParam("skuId") Long skuId);
}
legou-order/legou-order-service/src/main/java/com/lxs/legou/order/client/SkuClient.java
package com.lxs.legou.order.client;
import com.lxs.legou.item.api.SkuApi;
import com.lxs.legou.item.po.Sku;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@FeignClient(name = "item-service", fallback = SkuClient.SkuClientFallback.class)
public interface SkuClient extends SkuApi {
@Component
@RequestMapping("/sku-fallback")
//这个可以避免容器中requestMapping重复
class SkuClientFallback implements SkuClient {
private static final Logger LOGGER = LoggerFactory.getLogger(SkuClientFallback.class);
@Override
public List<Sku> selectSkusBySpuId(Long spuId) {
LOGGER.error("异常发生,进入fallback方法");
return null;
}
@Override
public Sku edit(Long id) {
LOGGER.error("异常发生,进入fallback方法");
return null;
}
@Override
public void decrCount(Integer num, Long skuId) {
LOGGER.error("异常发生,进入fallback方法");
}
}
}
2.3.3 调用库存递减
2.3.4 测试
下单后库存递减
2.3.5 超卖问题
如果库存递减使用下面的伪代码会产生超卖现象,超卖现象的本质都是多线程数据同步问题,因为时分布式系统,不能单纯的加锁处理,如果处理下面的逻辑需要使用分布式锁,关于分布式锁,后面内容讲解,因为我们的代码直接执行语句,有数据库行级锁,不会产生超卖问题
2.4 增加积分
比如每次下单完成之后,给用户增加10个积分,支付完成后赠送优惠券,优惠券可用于支付时再次抵扣。我们先完成增加积分功能。如下表:point表示用户积分
2.4.1 代码实现
dao
修改legou-security/legou-security-service/src/main/java/com/lxs/legou/security/dao/UserDao.java
/**
* 增加积分
* @param point
* @param userName
* @return
*/
@Update(value="update user_ set point_ = point_ + #{point} where user_name_ = #{userName}")
public int addPoint(@Param(value="point") Long point ,@Param(value="userName") String userName);
service
修改legou-security/legou-security-service/src/main/java/com/lxs/legou/security/service/IUserService.java
/**
* 增加会员积分
*/
public void addPoint(Long point, String username);
修改legou-security/legou-security-service/src/main/java/com/lxs/legou/security/service/impl/UserServiceImpl.java
@Override
public void addPoint(Long point, String username) {
((UserDao) getBaseMapper()).addPoint(point, username);
}
(3)控制层
修改legou-security/legou-security-service/src/main/java/com/lxs/legou/security/controller/UserController.java
/**
* 增加积分
* @param point
* @param username
*/
@GetMapping(value = "/add-point")
public void addPoint(@RequestParam("point") Long point, @RequestParam("username") String username) {
service.addPoint(point, username);
}
(4)Feign添加
修改legou-security/legou-security-instance/src/main/java/com/lxs/legou/security/api/UserApi.java
@GetMapping(value = "/add-point")
public void addPoint(@RequestParam("point") Long point, @RequestParam("username") String username);
2.4.2 增加积分调用
legou-order/legou-order-service/src/main/java/com/lxs/legou/order/service/impl/OrderServiceImpl.java
//4.增加积分 调用用户微服务的userfeign 增加积分
userClient.addPoint(10l, order.getUsername());