java电商项目(十)

本文涉及的产品
实时数仓Hologres,5000CU*H 100GB 3个月
智能开放搜索 OpenSearch行业算法版,1GB 20LCU 1个月
实时计算 Flink 版,5000CU*H 3个月
简介: 本文介绍了电商系统中订单结算和下单流程的实现。主要包括:1. **订单结页**: - **收件地址分析**:用户从购物车页面跳转到订单结算页,加载用户收件地址。地址信息存储在 `address_` 表中。 - **实现用户收件地址查询**:通过用户登录名查询收件地址,涉及实体类、DAO、Service 和 Controller 的实现。2. **下单**: - **业务分析**:下单时创建订单数据,包括订单表 `order_` 和订单明细表 `order_item_`,同时修改商品库存、增加用户积分并删除购物车数据。

[TOC]

1 订单结页

1.1 收件地址分析

用户从购物车页面点击结算,跳转到订单结算页,结算页需要加载用户对应的收件地址,如下图:

1558301821667

表结构分析:

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携带令牌访问

1565299241187

1.2.3 运送清单

1558302467737

运送清单其实就是购物车列表,直接查询之前的购物车列表即可,这里不做说明了。

2 下单

2.1 业务分析

点击结算页提交订单的时候,会立即创建订单数据,创建订单数据会将数据存入到2张表中,分别是订单表和订单明细表,此处还需要修改商品对应的库存数量,改变用户的积分,然后删除应该的购物车数据

1558302715038

订单表结构如下:

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中查询对应的购物车数据,然后执行库存减少,库存减少需要控制当前商品库存>=销售数量。

1558305399244

如何控制库存数量>=销售数量呢?其实可以通过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());

2.4.3 测试

目录
相关文章
|
17天前
|
消息中间件 安全 Java
java电商项目(十一)
本文接续前几个文章的项目进行讲解!
26 1
|
17天前
|
缓存 NoSQL Java
java电商项目(十二)
本文接续前几个文章的项目进行讲解
84 1
|
17天前
|
存储 NoSQL Java
java电商项目(九)
本文介绍了购物车功能的实现过程,包括用户登录后将商品添加至购物车、购物车列表展示及微服务之间的认证机制。具体步骤如下: 1. **购物车功能**: - 用户选择商品并点击“加入购物车”,系统将商品信息存储到Redis中。 2. **微服务之间认证**: - **传递管理员令牌**:在授权中心微服务调用用户微服务时,生成管理员令牌并通过Header传递。 - **传递当前用户令牌**:用户登录后,通过Feign拦截器将用户令牌传递到其他微服务。 - **获取用户数据**:通过`SecurityContextHolder`获取用户信息,并使用公钥解密令牌以验证用户
25 1
|
18天前
|
监控 算法 Java
java电商项目(七)
微服务网关作为系统唯一对外的入口,位于客户端和服务端之间,处理非业务功能,如路由请求、鉴权、监控、缓存、限流等。它解决了客户端直接调用多个微服务带来的复杂性、跨域请求、认证复杂、难以重构等问题。常用的微服务网关技术有Nginx、Zuul和Spring Cloud Gateway。Spring Cloud Gateway因其集成断路器、路径重写和较好的性能而被广泛使用。本文介绍了如何使用Spring Cloud Gateway搭建后台网关系统,包括引入依赖、配置文件、跨域配置、路由过滤配置、负载均衡、限流等。此外,还详细讲解了RBAC权限数据管理、组织机构管理单点登录(SSO)及JWT鉴权的实现
25 1
|
18天前
|
canal 监控 JavaScript
java电商项目(六)
Thymeleaf 是一个类似于 FreeMarker 的模板引擎,能够完全替代 JSP。它支持动静结合,无网络时显示静态内容,有网络时用后台数据替换静态内容,并且与 Spring Boot 完美整合。本文介绍了如何使用 Thymeleaf 生成商品详情页的静态页面。具体步骤包括创建商品静态化微服务、配置项目依赖、创建 Controller 和 Service、生成静态页面、模板填充、静态资源过滤以及启动测试。此外,还介绍了如何通过 Canal 监听商品数据变化,自动触发静态页面的生成或删除。
26 1
|
18天前
|
SQL 自然语言处理 Java
java电商项目(五)
本文介绍了如何构建一个基于Elasticsearch的商品搜索微服务,主要包括以下几个部分: 1. **数据导入ES**: - 搭建搜索工程,创建`legou-search`项目,提供搜索服务和索引数据更新操作。 - 配置`pom.xml`文件,引入必要的依赖。 - 创建启动器和配置文件,配置Elasticsearch连接信息。 - 分析索引库数据格式,确定需要存储的数据字段。 - 实现商品微服务接口,调用其他微服务获取所需数据。 - 创建索引并导入数据,将SPU和SKU数据转换为索引库中的Goods对象。 2. **商品搜索*
24 1
|
18天前
|
canal NoSQL 关系型数据库
java电商项目(四)
本章介绍了如何通过Lua、OpenResty、Nginx限流及Canal的使用,实现电商门户首页的高并发解决方案。主要内容包括: 1. **商城门户搭建**:使用Vue和iView构建前端门户项目,介绍如何展示商品分类和广告数据,并通过Redis缓存提升访问速度。 2. **Lua基础**:介绍Lua的基本概念、特性、应用场景及安装步骤,并通过示例展示了Lua的基本语法和常用功能。 3. **OpenResty介绍**:详细说明OpenResty的特性和优势,包括如何安装OpenResty和配置Nginx,以及如何使用Lua脚本操作Nginx缓存和数据库。
19 1
|
18天前
|
存储 前端开发 JavaScript
java电商项目(二)
本文档详细介绍了商品分类和规格参数的实现过程。商品分类分为三级管理,主要用于首页商品导航和后台商品管理,采用树状结构存储。规格参数则用于描述商品的具体属性,包括SPU和SKU的定义,规格参数与分类绑定,支持搜索过滤。文档涵盖了表结构设计、实体类、持久层、业务层、控制层的实现,并提供了前端组件的示例代码,确保前后端无缝对接。
30 1
|
17天前
|
存储 安全 Java
java电商项目(八)
OAuth 2.0 是一种开放标准,允许用户授权第三方应用访问其在某一网站上的私密资源,而无需提供用户名和密码。它通过提供一个令牌(token)来实现这一功能。OAuth 2.0 主要包括四种授权模式:授权码模式、简化模式、密码模式和客户端模式。授权码模式是最常用的一种,适用于第三方平台登录功能。Spring Security OAuth 2.0 提供了强大的工具来实现授权服务器和资源服务器的集成,支持多种授权模式和令牌存储方式,如内存、数据库、JWT 和
39 0
|
18天前
|
前端开发 算法 JavaScript
java电商项目(三)
本文介绍了乐购商城的商品数据分析和管理功能。首先解释了SPU(标准产品单位)和SKU(库存量单位)的概念,以及它们在商品管理和销售中的作用。接着详细分析了SPU、SPU详情和SKU三个表的结构及其关系。文章还介绍了商品管理的需求分析、实现思路和后台代码,包括实体类、持久层、业务层和控制层的实现。最后,文章讲解了前端组件的设计和实现,包括列表组件、添加修改组件、商品描述、通用规格、SKU特有规格和SKU列表的处理。通过这些内容,读者可以全面了解乐购商城的商品管理和数据分析系统。
29 0
下一篇
无影云桌面