迁移购物车模块

This commit is contained in:
YunaiV
2020-08-06 21:13:21 +08:00
parent d06e51ba21
commit 3914b32637
99 changed files with 810 additions and 269 deletions

View File

@@ -0,0 +1,13 @@
package cn.iocoder.mall.orderservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}

View File

@@ -0,0 +1,12 @@
package cn.iocoder.mall.orderservice.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* Spring Aop 配置类
*/
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
public class AopConfiguration {
}

View File

@@ -0,0 +1,28 @@
package cn.iocoder.mall.orderservice.config;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@MapperScan("cn.iocoder.mall.orderservice.dal.mysql.mapper") // 扫描对应的 Mapper 接口
@EnableTransactionManagement(proxyTargetClass = true) // 启动事务管理。
public class DatabaseConfiguration {
// 数据库连接池 Druid
@Bean
public ISqlInjector sqlInjector() {
return new DefaultSqlInjector(); // MyBatis Plus 逻辑删除
}
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor(); // MyBatis Plus 分页插件
}
}

View File

@@ -0,0 +1,18 @@
package cn.iocoder.mall.orderservice.convert.cart;
import cn.iocoder.mall.orderservice.dal.mysql.dataobject.cart.CartItemDO;
import cn.iocoder.mall.orderservice.rpc.cart.dto.CartItemAddReqDTO;
import cn.iocoder.mall.orderservice.service.cart.bo.CartItemAddBO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface CartConvert {
CartConvert INSTANCE = Mappers.getMapper(CartConvert.class);
CartItemDO convert(CartItemAddBO bean);
CartItemAddBO convert(CartItemAddReqDTO bean);
}

View File

@@ -0,0 +1,73 @@
package cn.iocoder.mall.orderservice.dal.mysql.dataobject.cart;
import cn.iocoder.mall.mybatis.core.dataobject.DeletableDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 购物车的商品信息 DO
*/
@TableName("cart_item")
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class CartItemDO extends DeletableDO {
// ========= 基础字段 BEGIN =========
/**
* 编号,唯一自增。
*/
private Integer id;
/**
* 是否选中
*/
private Boolean selected;
// ========= 基础字段 END =========
// ========= 买家信息 BEGIN =========
/**
* 用户编号
*/
private Integer userId;
// ========= 买家信息 END =========
// ========= 商品信息 BEGIN =========
/**
* 商品 SPU 编号
*/
private Integer spuId;
/**
* 商品 SKU 编号
*/
private Integer skuId;
/**
* 商品购买数量
*/
private Integer quantity;
// TODO 冗余字段
// ========= 商品信息 END =========
// ========= 优惠信息 BEGIN =========
// /**
// * 商品营销活动编号
// */
// private Integer activityId;
// /**
// * 商品营销活动类型
// */
// private Integer activityType;
// ========= 优惠信息 END =========
}

View File

@@ -0,0 +1,32 @@
package cn.iocoder.mall.orderservice.dal.mysql.mapper.cart;
import cn.iocoder.mall.mybatis.core.query.QueryWrapperX;
import cn.iocoder.mall.orderservice.dal.mysql.dataobject.cart.CartItemDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.List;
import java.util.Set;
@Repository
public interface CartItemMapper extends BaseMapper<CartItemDO> {
default CartItemDO selectByUserIdAndSkuId(Integer userId, Integer skuId) {
return selectOne(new QueryWrapper<CartItemDO>().eq("user_id", userId)
.eq("sku_id", skuId));
}
default List<CartItemDO> selectListByUserIdAndSkuIds(Integer userId, Collection<Integer> skuIds) {
return selectList(new QueryWrapperX<CartItemDO>().eq("user_id", userId)
.inIfPresent("sku_id", skuIds));
}
default void updateByIds(@Param("ids") Set<Integer> ids, @Param("updateObject") CartItemDO updateObject) {
// TODO 芋艿batch update ,在 mybatis plus 做拓展,这里先临时处理
ids.forEach(id -> updateById(updateObject.setId(id)));
}
}

View File

@@ -0,0 +1,65 @@
package cn.iocoder.mall.orderservice.manager.cart;
import cn.iocoder.common.framework.enums.CommonStatusEnum;
import cn.iocoder.common.framework.exception.util.ServiceExceptionUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.orderservice.convert.cart.CartConvert;
import cn.iocoder.mall.orderservice.rpc.cart.dto.CartItemAddReqDTO;
import cn.iocoder.mall.orderservice.rpc.cart.dto.CartItemUpdateQuantityReqDTO;
import cn.iocoder.mall.orderservice.service.cart.CartService;
import cn.iocoder.mall.productservice.rpc.sku.ProductSkuRpc;
import cn.iocoder.mall.productservice.rpc.sku.dto.ProductSkuRespDTO;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import static cn.iocoder.mall.orderservice.enums.OrderErrorCodeConstants.CARD_ITEM_SKU_NOT_FOUND;
/**
* 购物车 Manager
*/
@Service
public class CartManager {
@DubboReference(version = "${dubbo.consumer.ProductSkuRpc.version}")
private ProductSkuRpc productSkuRpc;
@Autowired
private CartService cartService;
/**
* 添加商品到购物车
*
* @param addReqDTO 添加商品信息
*/
public void addCartItem(CartItemAddReqDTO addReqDTO) {
// 校验商品 SKU 是否合法
ProductSkuRespDTO skuDTO = this.checkProductSku(addReqDTO.getSkuId());
// 添加购物车项
cartService.addCartItem(CartConvert.INSTANCE.convert(addReqDTO), skuDTO.getQuantity());
}
/**
* 更新购物车商品数量
*
* @param updateQuantityReqDTO 更新商品数量
*/
public void updateCartItemSelected(CartItemUpdateQuantityReqDTO updateQuantityReqDTO) {
// 校验商品 SKU 是否合法
ProductSkuRespDTO skuDTO = this.checkProductSku(updateQuantityReqDTO.getSkuId());
// 更新购物车商品数量
cartService.updateCartItemQuantity(updateQuantityReqDTO.getUserId(), updateQuantityReqDTO.getSkuId(),
updateQuantityReqDTO.getQuantity(), skuDTO.getQuantity());
}
private ProductSkuRespDTO checkProductSku(Integer skuId) {
CommonResult<ProductSkuRespDTO> getProductSkuResult = productSkuRpc.getProductSku(skuId);
getProductSkuResult.checkError();
ProductSkuRespDTO skuDTO = getProductSkuResult.getData();
if (skuDTO == null || CommonStatusEnum.DISABLE.getValue().equals(skuDTO.getStatus())) {
throw ServiceExceptionUtil.exception(CARD_ITEM_SKU_NOT_FOUND);
}
return skuDTO;
}
}

View File

@@ -0,0 +1 @@
package cn.iocoder.mall.orderservice.manager;

View File

@@ -0,0 +1,28 @@
package cn.iocoder.mall.orderservice.rpc.cart;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.orderservice.manager.cart.CartManager;
import cn.iocoder.mall.orderservice.rpc.cart.dto.CartItemAddReqDTO;
import cn.iocoder.mall.orderservice.rpc.cart.dto.CartItemUpdateQuantityReqDTO;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
@DubboService
public class CartRpcImpl implements CartRpc {
@Autowired
private CartManager cartManager;
@Override
public CommonResult<Boolean> addCartItem(CartItemAddReqDTO addItemReqDTO) {
cartManager.addCartItem(addItemReqDTO);
return CommonResult.success(true);
}
@Override
public CommonResult<Boolean> updateCartItemSelected(CartItemUpdateQuantityReqDTO updateQuantityReqDTO) {
cartManager.updateCartItemSelected(updateQuantityReqDTO);
return CommonResult.success(true);
}
}

View File

@@ -0,0 +1,111 @@
package cn.iocoder.mall.orderservice.service.cart;
import cn.iocoder.common.framework.exception.util.ServiceExceptionUtil;
import cn.iocoder.common.framework.util.CollectionUtils;
import cn.iocoder.mall.orderservice.convert.cart.CartConvert;
import cn.iocoder.mall.orderservice.dal.mysql.dataobject.cart.CartItemDO;
import cn.iocoder.mall.orderservice.dal.mysql.mapper.cart.CartItemMapper;
import cn.iocoder.mall.orderservice.service.cart.bo.CartItemAddBO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
import java.util.List;
import static cn.iocoder.mall.orderservice.enums.OrderErrorCodeConstants.CARD_ITEM_NOT_FOUND;
import static cn.iocoder.mall.orderservice.enums.OrderErrorCodeConstants.CARD_ITEM_SKU_QUANTITY_NOT_ENOUGH;
/**
* 购物车 Service
*/
@Service
@Validated
public class CartService {
@Autowired
private CartItemMapper cartItemMapper;
/**
* 添加商品到购物车
*
* @param addBO 添加商品信息
* @param skuQuantity 商品 SKU 的库存,主要用于库存校验
*/
public void addCartItem(@Valid CartItemAddBO addBO, Integer skuQuantity) {
// 查询 CartItemDO
CartItemDO itemDO = cartItemMapper.selectByUserIdAndSkuId(addBO.getUserId(), addBO.getSkuId());
// 存在,则进行数量更新
if (itemDO != null) {
if (addBO.getQuantity() + itemDO.getQuantity() > skuQuantity) {
// 校验库存
throw ServiceExceptionUtil.exception(CARD_ITEM_SKU_QUANTITY_NOT_ENOUGH);
}
cartItemMapper.updateById(new CartItemDO().setId(itemDO.getId()).setSelected(true)
.setQuantity(addBO.getQuantity() + itemDO.getQuantity()));
return;
}
// 不存在,则进行插入
if (addBO.getQuantity() > skuQuantity) {
// 校验库存
throw ServiceExceptionUtil.exception(CARD_ITEM_SKU_QUANTITY_NOT_ENOUGH);
}
cartItemMapper.insert(CartConvert.INSTANCE.convert(addBO).setSelected(true));
}
/**
* 更新购物车商品数量
*
* @param userId 用户编号
* @param skuId 商品 SKU 编号
* @param quantity 数量
* @param skuQuantity 商品 SKU 的库存,主要用于库存校验
*/
public void updateCartItemQuantity(Integer userId, Integer skuId, Integer quantity, Integer skuQuantity) {
if (quantity > skuQuantity) {
// 校验库存
throw ServiceExceptionUtil.exception(CARD_ITEM_SKU_QUANTITY_NOT_ENOUGH);
}
// 查询 CartItemDO
CartItemDO itemDO = cartItemMapper.selectByUserIdAndSkuId(userId, skuId);
if (itemDO == null) {
throw ServiceExceptionUtil.exception(CARD_ITEM_NOT_FOUND);
}
// 更新数量
cartItemMapper.updateById(new CartItemDO().setId(itemDO.getId()).setQuantity(quantity));
}
/**
* 更新购物车商品是否选中
*
* @param userId 用户编号
* @param skuIds 商品 SKU 编号数组
* @param selected 是否选中
*/
public void updateCartItemSelected(Integer userId, List<Integer> skuIds, Boolean selected) {
// 查询 CartItemDO 列表
List<CartItemDO> itemDOs = cartItemMapper.selectListByUserIdAndSkuIds(userId, skuIds);
if (skuIds.size() != itemDOs.size()) {
throw ServiceExceptionUtil.exception(CARD_ITEM_NOT_FOUND);
}
// 更新选中
cartItemMapper.updateByIds(CollectionUtils.convertSet(itemDOs, CartItemDO::getId),
new CartItemDO().setSelected(selected));
}
/**
* 购物车删除商品
*
* @param userId 用户编号
* @param skuIds 商品 SKU 编号的数组
*/
public void deleteList(Integer userId, List<Integer> skuIds) {
// 查询 CartItemDO 列表
List<CartItemDO> itemDOs = cartItemMapper.selectListByUserIdAndSkuIds(userId, skuIds);
// 批量标记删除
cartItemMapper.deleteBatchIds(CollectionUtils.convertSet(itemDOs, CartItemDO::getId));
}
}

View File

@@ -0,0 +1,38 @@
package cn.iocoder.mall.orderservice.service.cart.bo;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
/**
* 购物车添加购物项 Request DTO
*/
@Data
@Accessors
public class CartItemAddBO {
/**
* 用户编号
*/
@NotNull(message = "用户编号不能为空")
private Integer userId;
/**
* 商品 SPU 编号
*/
@NotNull(message = "商品 SPU 编号不能为空")
private Integer spuId;
/**
* 商品 SKU 编号
*/
@NotNull(message = "商品 SKU 编号不能为空")
private Integer skuId;
/**
* 数量
*/
@NotNull(message = "数量不能为空")
@Min(message = "数量必须大于 0", value = 1L)
private Integer quantity;
}

View File

@@ -0,0 +1,65 @@
package cn.iocoder.mall.orderservice.service.cart.bo;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 购物车的商品信息 BO
*/
@Data
@Accessors(chain = true)
public class CartItemBO {
// ========= 基础字段 BEGIN =========
/**
* 编号,唯一自增。
*/
private Integer id;
/**
* 是否选中
*/
private Boolean selected;
// ========= 基础字段 END =========
// ========= 买家信息 BEGIN =========
/**
* 用户编号
*/
private Integer userId;
// ========= 买家信息 END =========
// ========= 商品信息 BEGIN =========
/**
* 商品 SPU 编号
*/
private Integer spuId;
/**
* 商品 SKU 编号
*/
private Integer skuId;
/**
* 商品购买数量
*/
private Integer quantity;
// ========= 商品信息 END =========
// ========= 优惠信息 BEGIN =========
// /**
// * 商品营销活动编号
// */
// private Integer activityId;
// /**
// * 商品营销活动类型
// */
// private Integer activityType;
// ========= 优惠信息 END =========
}

View File

@@ -0,0 +1,21 @@
spring:
# 数据源配置项
datasource:
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_order?useSSL=false&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 3WLiVUBEwTbvAfsh
# Spring Cloud 配置项
cloud:
nacos:
# Spring Cloud Nacos Discovery 配置项
discovery:
server-addr: 400-infra.server.iocoder.cn:8848 # Nacos 服务器地址
namespace: dev # Nacos 命名空间
# Dubbo 配置项
dubbo:
# Dubbo 注册中心
registry:
# address: spring-cloud://400-infra.server.iocoder.cn:8848 # 指定 Dubbo 服务注册中心的地址
address: nacos://400-infra.server.iocoder.cn:8848?namespace=dev # 指定 Dubbo 服务注册中心的地址

View File

@@ -0,0 +1,24 @@
spring:
# 数据源配置项
datasource:
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/mall_order?useSSL=false&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 3WLiVUBEwTbvAfsh
# Spring Cloud 配置项
cloud:
nacos:
# Spring Cloud Nacos Discovery 配置项
discovery:
server-addr: 400-infra.server.iocoder.cn:8848 # Nacos 服务器地址
namespace: dev # Nacos 命名空间
# Dubbo 配置项
dubbo:
# Dubbo 注册中心
registry:
# address: spring-cloud://400-infra.server.iocoder.cn:8848 # 指定 Dubbo 服务注册中心的地址
address: nacos://400-infra.server.iocoder.cn:8848?namespace=dev # 指定 Dubbo 服务注册中心的地址
# Dubbo 服务提供者的配置
provider:
tag: ${DUBBO_TAG} # Dubbo 路由分组

View File

@@ -0,0 +1,60 @@
spring:
# Application 的配置项
application:
name: order-service
# Profile 的配置项
profiles:
active: local
# MyBatis Plus 配置项
mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
global-config:
db-config:
id-type: auto
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: cn.iocoder.mall.orderservice.dal.mysql.dataobject
# Dubbo 配置项
dubbo:
# Spring Cloud Alibaba Dubbo 专属配置
cloud:
subscribed-services: '' # 设置订阅的应用列表,默认为 * 订阅所有应用
# Dubbo 提供者的协议
protocol:
name: dubbo
port: -1
# Dubbo 提供服务的扫描基础包
scan:
base-packages: cn.iocoder.mall.orderservice.rpc
# Dubbo 服务提供者的配置
provider:
filter: -exception
validation: true # 开启 Provider 参数校验
version: 1.0.0 # 服务的版本号
# Dubbo 服务消费者的配置
consumer:
ErrorCodeRpc:
version: 1.0.0
# RocketMQ 配置项
rocketmq:
name-server: 400-infra.server.iocoder.cn:9876
producer:
group: ${spring.application.name}-producer-group
# Actuator 监控配置项
management:
server.port: 38084 # 独立端口,避免被暴露出去
endpoints.web.exposure.include: '*' # 暴露所有监控端点
server.port: ${management.server.port} # 设置使用 Actuator 的服务器端口,因为 RPC 服务不需要 Web 端口
# Mall 配置项
mall:
# 错误码配置项对应 ErrorCodeProperties 配置类
error-code:
group: ${spring.application.name}
constants-class: cn.iocoder.mall.orderservice.enums.OrderErrorCodeConstants