提交 b7b21b6a 编写于 作者: 有来技术

refactor:商品优化

上级 a4f985a7
package com.youlai.mall.oms.pojo.dto;
import lombok.*;
import java.io.Serializable;
/**
* 购物车商品传输层实体
*/
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
@Data
public class CartItemDTO implements Serializable {
private Long skuId;
/**
* 商品库存单元名称
*/
private String skuName;
/**
* 商品库存单元编码
*/
private String skuSn;
/**
* 商品库存单元图片
*/
private String picUrl;
private Integer count; // 商品数量
/**
* 加入购物车价格,因会变动,不能作为订单计算因子,订单验价时需重新获取商品价格即可
*/
private Long price;
private Long coupon;
private Boolean checked;
/**
* 商品库存数量,页面控制能选择最大数量
*/
private Integer stock;
/**
* 商品名称
*/
private String goodsName;
}
package com.youlai.mall.oms.pojo.vo;
import lombok.*;
import java.io.Serializable;
import java.util.List;
/**
* 购物车实体类
*/
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
@Data
public class CartVO implements Serializable {
private List<CartItem> items;
@Data
public static class CartItem {
private Long skuId;
private String skuName; // 标题
private String skuCode;
private String pic;
private Integer count; // 商品数量
private Long price; // 加入购物车价格,因会变动,不能作为订单计算因子,订单验价时需重新获取商品价格即可
private Long coupon;
private Boolean checked;
private Integer stock;// 商品库存数量,页面控制能选择最大数量
private String spuName;
}
}
......@@ -15,10 +15,5 @@ public interface OmsConstants {
*/
String RELEASE_LOCK_LUA_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
/**
* 释放锁成功返回值
*/
Long RELEASE_LOCK_SUCCESS_RESULT = 1L;
}
......@@ -2,7 +2,8 @@ package com.youlai.mall.oms.controller.app;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.youlai.common.result.Result;
import com.youlai.mall.oms.pojo.vo.CartVO;
import com.youlai.common.web.util.JwtUtils;
import com.youlai.mall.oms.pojo.dto.CartItemDTO;
import com.youlai.mall.oms.service.ICartService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
......@@ -11,6 +12,8 @@ import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author huawei
* @email huawei_code@163.com
......@@ -30,8 +33,9 @@ public class CartController {
@GetMapping
@ApiOperationSupport(order = 1)
public Result getCart() {
CartVO cart = cartService.getCart();
return Result.success(cart);
Long memberId = JwtUtils.getUserId();
List<CartItemDTO> result = cartService.listCartItemByMemberId(memberId);
return Result.success(result);
}
@ApiOperation(value = "删除购物车")
......@@ -54,7 +58,7 @@ public class CartController {
@ApiOperation(value = "更新购物车商品")
@PutMapping("/skuId/{skuId}")
@ApiOperationSupport(order = 4)
public Result updateCartItem(@PathVariable Long skuId,@RequestBody CartVO.CartItem cartItem) {
public Result updateCartItem(@PathVariable Long skuId,@RequestBody CartItemDTO cartItem) {
cartItem.setSkuId(skuId);
boolean result = cartService.updateCartItem(cartItem);
return Result.judge(result);
......
......@@ -5,14 +5,15 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.common.result.Result;
import com.youlai.common.web.util.JwtUtils;
import com.youlai.mall.oms.enums.PayTypeEnum;
import com.youlai.mall.oms.pojo.entity.OmsOrder;
import com.youlai.mall.oms.pojo.dto.OrderConfirmDTO;
import com.youlai.mall.oms.pojo.dto.OrderSubmitDTO;
import com.youlai.mall.oms.pojo.entity.OmsOrder;
import com.youlai.mall.oms.pojo.vo.OrderConfirmVO;
import com.youlai.mall.oms.pojo.vo.OrderSubmitVO;
import com.youlai.mall.oms.pojo.dto.OrderSubmitDTO;
import com.youlai.mall.oms.service.IOrderService;
import io.swagger.annotations.*;
import lombok.AllArgsConstructor;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
......@@ -28,18 +29,13 @@ import javax.validation.Valid;
@RestController
@RequestMapping("/app-api/v1/orders")
@Slf4j
@AllArgsConstructor
@RequiredArgsConstructor
public class OrderController {
private IOrderService orderService;
final IOrderService orderService;
@ApiOperation("订单列表")
@GetMapping
@ApiImplicitParams({
@ApiImplicitParam(name = "page", defaultValue = "1", value = "页码", paramType = "query", dataType = "Long"),
@ApiImplicitParam(name = "limit", defaultValue = "10", value = "每页数量", paramType = "query", dataType = "Long"),
@ApiImplicitParam(name = "status", value = "订单状态", paramType = "query", dataType = "Integer")
})
public Result list(
@RequestParam(defaultValue = "1") Long page,
@RequestParam(defaultValue = "10") Long limit,
......@@ -53,7 +49,6 @@ public class OrderController {
}
@ApiOperation("订单确认")
@ApiImplicitParam(name = "orderConfirm", value = "确认订单信息", required = true, paramType = "body", dataType = "OrderConfirmDTO")
@PostMapping("/_confirm")
public Result<OrderConfirmVO> confirm(@RequestBody OrderConfirmDTO orderConfirm) {
OrderConfirmVO result = orderService.confirm(orderConfirm);
......@@ -61,7 +56,6 @@ public class OrderController {
}
@ApiOperation("订单提交")
@ApiImplicitParam(name = "orderSubmitDTO", value = "提交订单信息", required = true, paramType = "body", dataType = "orderSubmitDTO")
@PostMapping("/_submit")
public Result submit(@Valid @RequestBody OrderSubmitDTO orderSubmitDTO) {
OrderSubmitVO result = orderService.submit(orderSubmitDTO);
......@@ -70,10 +64,6 @@ public class OrderController {
@ApiOperation("订单支付")
@PostMapping("/{orderId}/_pay")
@ApiImplicitParams({
@ApiImplicitParam(name = "orderId", value = "订单ID", paramType = "path", dataType = "Long"),
@ApiImplicitParam(name = "payType", value = "支付方式", paramType = "query", dataType = "Integer")
})
public Result pay(@PathVariable Long orderId, Integer payType) {
PayTypeEnum payTypeEnum = PayTypeEnum.getByCode(payType);
switch (payTypeEnum) {
......@@ -88,7 +78,6 @@ public class OrderController {
@ApiOperation("订单删除")
@PostMapping("/{orderId}")
@ApiImplicitParam(name = "orderId", value = "订单ID", paramType = "path", dataType = "Long")
public Result deleteOrder(@PathVariable Long orderId) {
boolean result = orderService.deleteOrder(orderId);
return Result.judge(result);
......
package com.youlai.mall.oms.service;
import com.youlai.mall.oms.pojo.vo.CartVO;
import com.youlai.mall.oms.pojo.dto.CartItemDTO;
import java.util.List;
......@@ -10,15 +10,14 @@ import java.util.List;
public interface ICartService {
CartVO getCart();
List<CartVO.CartItem> getCartItems(Long memberId);
List<CartItemDTO> listCartItemByMemberId(Long memberId);
boolean deleteCart();
boolean addCartItem(Long skuId);
boolean updateCartItem(CartVO.CartItem cartItem);
boolean updateCartItem(CartItemDTO cartItem);
boolean removeCartItem(Long skuId);
......
......@@ -2,7 +2,7 @@ package com.youlai.mall.oms.service.impl;
import com.youlai.common.web.util.JwtUtils;
import com.youlai.mall.oms.constant.OmsConstants;
import com.youlai.mall.oms.pojo.vo.CartVO;
import com.youlai.mall.oms.pojo.dto.CartItemDTO;
import com.youlai.mall.oms.service.ICartService;
import com.youlai.mall.pms.api.GoodsFeignClient;
import com.youlai.mall.pms.pojo.dto.app.SkuDTO;
......@@ -34,23 +34,10 @@ public class CartServiceImpl implements ICartService {
private RedisTemplate redisTemplate;
private GoodsFeignClient skuFeignService;
/**
* 获取用户购物车
*/
@Override
public CartVO getCart() {
CartVO cart = new CartVO();
Long memberId= JwtUtils.getUserId();
BoundHashOperations cartHashOperations = getCartHashOperations(memberId);
List<CartVO.CartItem> cartItems = cartHashOperations.values();
cart.setItems(cartItems);
return cart;
}
@Override
public List<CartVO.CartItem> getCartItems(Long memberId) {
public List<CartItemDTO> listCartItemByMemberId(Long memberId) {
BoundHashOperations cartHashOperations = getCartHashOperations(memberId);
List<CartVO.CartItem> cartItems = cartHashOperations.values();
List<CartItemDTO> cartItems = cartHashOperations.values();
return cartItems;
}
......@@ -73,28 +60,28 @@ public class CartServiceImpl implements ICartService {
BoundHashOperations cartHashOperations = getCartHashOperations(memberId);
String hKey = skuId + "";
CartVO.CartItem cartItem;
CartItemDTO cartItem;
// 购物车已存在该商品,更新商品数量
if (cartHashOperations.get(hKey) != null) {
cartItem = (CartVO.CartItem) cartHashOperations.get(hKey);
cartItem = (CartItemDTO) cartHashOperations.get(hKey);
cartItem.setCount(cartItem.getCount() + 1); // 点击一次“加入购物车”,数量+1
cartItem.setChecked(true);
cartHashOperations.put(hKey, cartItem);
return true;
}
// 购物车不存在该商品,添加商品至购物车
cartItem = new CartVO.CartItem();
cartItem = new CartItemDTO();
CompletableFuture<Void> cartItemCompletableFuture = CompletableFuture.runAsync(() -> {
SkuDTO sku = skuFeignService.getSkuById(skuId).getData();
if (sku != null) {
cartItem.setSkuId(sku.getId());
cartItem.setCount(1);
cartItem.setPrice(sku.getPrice());
cartItem.setPic(sku.getPic());
cartItem.setPicUrl(sku.getPicUrl());
cartItem.setSkuName(sku.getName());
cartItem.setStock(sku.getStock());
cartItem.setSkuCode(sku.getCode());
cartItem.setSpuName(sku.getSpuName());
cartItem.setSkuSn(sku.getSn());
cartItem.setGoodsName(sku.getGoodsName());
cartItem.setChecked(true);
}
});
......@@ -107,12 +94,12 @@ public class CartServiceImpl implements ICartService {
* 更新购物车总商品数量、选中状态
*/
@Override
public boolean updateCartItem(CartVO.CartItem cartItem) {
public boolean updateCartItem(CartItemDTO cartItem) {
Long memberId= JwtUtils.getUserId();
BoundHashOperations cartHashOperations = getCartHashOperations(memberId);
String hKey = cartItem.getSkuId() + "";
if (cartHashOperations.get(hKey) != null) {
CartVO.CartItem cacheCartItem = (CartVO.CartItem) cartHashOperations.get(hKey);
CartItemDTO cacheCartItem = (CartItemDTO) cartHashOperations.get(hKey);
if(cartItem.getChecked()!=null){
cacheCartItem.setChecked(cartItem.getChecked());
}
......@@ -145,7 +132,7 @@ public class CartServiceImpl implements ICartService {
Long memberId= JwtUtils.getUserId();
BoundHashOperations cartHashOperations = getCartHashOperations(memberId);
for (Object value : cartHashOperations.values()) {
CartVO.CartItem cartItem = (CartVO.CartItem) value;
CartItemDTO cartItem = (CartItemDTO) value;
cartItem.setChecked(checked);
String hKey = cartItem.getSkuId() + "";
cartHashOperations.put(hKey, cartItem);
......@@ -163,7 +150,7 @@ public class CartServiceImpl implements ICartService {
Long memberId= JwtUtils.getUserId();
BoundHashOperations cartHashOperations = getCartHashOperations(memberId);
for (Object value : cartHashOperations.values()) {
CartVO.CartItem cartItem = (CartVO.CartItem) value;
CartItemDTO cartItem = (CartItemDTO) value;
if (cartItem.getChecked()) {
cartHashOperations.delete(cartItem.getSkuId()+"");
}
......
package com.youlai.mall.oms.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
......@@ -20,20 +21,21 @@ import com.youlai.mall.oms.pojo.entity.OmsOrderItem;
import com.youlai.mall.oms.pojo.dto.OrderConfirmDTO;
import com.youlai.mall.oms.pojo.dto.OrderItemDTO;
import com.youlai.mall.oms.pojo.dto.OrderSubmitDTO;
import com.youlai.mall.oms.pojo.vo.CartVO;
import com.youlai.mall.oms.pojo.dto.CartItemDTO;
import com.youlai.mall.oms.pojo.vo.OrderConfirmVO;
import com.youlai.mall.oms.pojo.vo.OrderSubmitVO;
import com.youlai.mall.oms.service.ICartService;
import com.youlai.mall.oms.service.IOrderItemService;
import com.youlai.mall.oms.service.IOrderService;
import com.youlai.mall.pms.api.GoodsFeignClient;
import com.youlai.mall.pms.api.StockFeignClient;
import com.youlai.mall.pms.pojo.dto.app.SkuDTO;
import com.youlai.mall.pms.pojo.dto.app.SkuLockDTO;
import com.youlai.mall.pms.pojo.dto.app.LockStockDTO;
import com.youlai.mall.ums.api.MemberAddressFeignClient;
import com.youlai.mall.ums.api.MemberFeignClient;
import com.youlai.mall.ums.pojo.entity.UmsAddress;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
......@@ -51,21 +53,23 @@ import java.util.stream.Collectors;
import static com.youlai.mall.oms.constant.OmsConstants.*;
@AllArgsConstructor
@RequiredArgsConstructor
@Slf4j
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> implements IOrderService {
private ICartService cartService;
private GoodsFeignClient skuFeignService;
private MemberAddressFeignClient addressFeignService;
private IOrderItemService orderItemService;
private RabbitTemplate rabbitTemplate;
private StringRedisTemplate redisTemplate;
private ThreadPoolExecutor threadPoolExecutor;
private MemberFeignClient memberFeignClient;
final ICartService cartService;
final MemberAddressFeignClient addressFeignService;
final IOrderItemService orderItemService;
final RabbitTemplate rabbitTemplate;
final StringRedisTemplate redisTemplate;
final ThreadPoolExecutor threadPoolExecutor;
final MemberFeignClient memberFeignClient;
final BusinessNoGenerator businessNoGenerator;
private BusinessNoGenerator businessNoGenerator;
final GoodsFeignClient goodsFeignClient;
final StockFeignClient stockFeignClient;
/**
* 订单确认
......@@ -83,25 +87,25 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
.skuId(orderConfirmDTO.getSkuId())
.count(orderConfirmDTO.getCount())
.build();
SkuDTO sku = skuFeignService.getSkuById(orderConfirmDTO.getSkuId()).getData();
SkuDTO sku = goodsFeignClient.getSkuById(orderConfirmDTO.getSkuId()).getData();
orderItemDTO.setPrice(sku.getPrice());
orderItemDTO.setPic(sku.getPic());
orderItemDTO.setPic(sku.getPicUrl());
orderItemDTO.setSkuName(sku.getName());
orderItemDTO.setSkuCode(sku.getCode());
orderItemDTO.setSpuName(sku.getSpuName());
orderItemDTO.setSkuCode(sku.getSn());
orderItemDTO.setSpuName(sku.getGoodsName());
orderItems.add(orderItemDTO);
} else { // 购物车中商品结算
List<CartVO.CartItem> cartItems = cartService.getCartItems(memberId);
List<CartItemDTO> cartItems = cartService.listCartItemByMemberId(memberId);
List<OrderItemDTO> items = cartItems.stream()
.filter(CartVO.CartItem::getChecked)
.filter(CartItemDTO::getChecked)
.map(cartItem -> OrderItemDTO.builder()
.skuId(cartItem.getSkuId())
.count(cartItem.getCount())
.price(cartItem.getPrice())
.skuName(cartItem.getSkuName())
.skuCode(cartItem.getSkuCode())
.spuName(cartItem.getSpuName())
.pic(cartItem.getPic())
.skuCode(cartItem.getSkuSn())
.spuName(cartItem.getGoodsName())
.pic(cartItem.getPicUrl())
.build())
.collect(Collectors.toList());
orderItems.addAll(items);
......@@ -124,7 +128,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
}, threadPoolExecutor);
CompletableFuture.allOf(orderItemsCompletableFuture, addressesCompletableFuture, orderTokenCompletableFuture).join();
log.info("订单确认响应:{}", orderConfirmVO.toString());
log.info("订单确认响应:{}", orderConfirmVO);
return orderConfirmVO;
}
......@@ -138,43 +142,34 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
// 订单重复提交校验
String orderToken = submitDTO.getOrderToken();
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(RELEASE_LOCK_LUA_SCRIPT, Long.class);
Long result = this.redisTemplate.execute(redisScript, Collections.singletonList(ORDER_TOKEN_PREFIX + orderToken), orderToken);
if (!ObjectUtil.equals(result, RELEASE_LOCK_SUCCESS_RESULT)) {
throw new BizException("订单不可重复提交");
}
Long execute = this.redisTemplate.execute(redisScript, Collections.singletonList(ORDER_TOKEN_PREFIX + orderToken), orderToken);
Assert.isTrue(execute.equals(1l),"订单不可重复提交");
List<OrderItemDTO> orderItems = submitDTO.getOrderItems();
if (CollectionUtil.isEmpty(orderItems)) {
throw new BizException("订单没有商品,请选择商品后提交");
}
Assert.isTrue(CollectionUtil.isNotEmpty(orderItems),"订单商品为空");
// 订单验价
Long currentTotalPrice = orderItems.stream().map(item -> {
SkuDTO sku = skuFeignService.getSkuById(item.getSkuId()).getData();
SkuDTO sku = goodsFeignClient.getSkuById(item.getSkuId()).getData();
if (sku != null) {
return sku.getPrice() * item.getCount();
}
return 0l;
}).reduce(0l, Long::sum);
if (currentTotalPrice.compareTo(submitDTO.getTotalPrice()) != 0) {
throw new BizException("页面已过期,请重新刷新页面再提交");
}
Assert.isTrue(currentTotalPrice.compareTo(submitDTO.getTotalPrice()) == 0,"当前页面已过期,请重新刷新页面再提交");
// 校验库存是否足够和锁库存
List<SkuLockDTO> skuLockList = orderItems.stream()
.map(item -> SkuLockDTO.builder().skuId(item.getSkuId())
List<LockStockDTO> skuLockList = orderItems.stream()
.map(item -> LockStockDTO.builder().skuId(item.getSkuId())
.count(item.getCount())
.orderToken(orderToken)
.build())
.collect(Collectors.toList());
Result lockResult = skuFeignService.lockStock(skuLockList);
Result lockResult = stockFeignClient.lockStock(skuLockList);
if (!Result.success().getCode().equals(lockResult.getCode())) {
throw new BizException(Result.failed().getMsg());
}
Assert.isTrue(Result.success().getCode().equals(lockResult.getCode()),"锁定商品库存失败:{}",lockResult.getMsg());
// 创建订单(状态:待支付)
OmsOrder order = new OmsOrder();
......@@ -209,7 +204,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
OrderSubmitVO submitVO = new OrderSubmitVO();
submitVO.setOrderId(order.getId());
submitVO.setOrderSn(order.getOrderSn());
log.info("订单提交响应:{}", submitVO.toString());
log.info("订单提交响应:{}", submitVO);
return submitVO;
}
......@@ -238,7 +233,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
}
// 扣减库存
Result deductStockResult = skuFeignService.deductStock(order.getOrderSn());
Result deductStockResult = stockFeignClient.deductStock(order.getOrderSn());
if (!Result.isSuccess(deductStockResult)) {
throw new BizException("扣减商品库存失败");
}
......@@ -279,7 +274,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
boolean result = this.updateById(order);
if (result) {
// 释放被锁定的库存
Result unlockResult = skuFeignService.unlockStock(order.getOrderSn());
Result unlockResult = stockFeignClient.unlockStock(order.getOrderSn());
if (!Result.isSuccess(unlockResult)) {
throw new BizException(unlockResult.getMsg());
}
......
......@@ -6,12 +6,12 @@ import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "mall-pms")
@FeignClient(value = "mall-pms",contextId = "goods")
public interface GoodsFeignClient {
/**
* 获取商品信息
*/
@GetMapping("/app-api/v1/goods/{id}")
@GetMapping("/app-api/v1/stocks/{id}")
Result<SkuDTO> getSkuById(@PathVariable Long id);
}
package com.youlai.mall.pms.api;
import com.youlai.common.result.Result;
import com.youlai.mall.pms.pojo.dto.app.SkuLockDTO;
import com.youlai.mall.pms.pojo.dto.app.LockStockDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@FeignClient(value = "mall-pms")
@FeignClient(value = "mall-pms",contextId = "stock")
public interface StockFeignClient {
/**
* 锁定库存
*/
@PutMapping("/app-api/v1/stocks/_lock")
Result lockStock(@RequestBody List<SkuLockDTO> list);
Result lockStock(@RequestBody List<LockStockDTO> list);
/**
* 解锁库存
......
......@@ -14,7 +14,7 @@ import lombok.NoArgsConstructor;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SkuLockDTO {
public class LockStockDTO {
private Long skuId;
......
......@@ -12,12 +12,12 @@ import lombok.Data;
public class SkuDTO {
private Long id;
private String code;
private String sn;
private String name;
private String pic;
private String picUrl;
private Long price;
private Integer stock;
private String spuName;
private String goodsName;
}
package com.youlai.mall.pms.pojo.vo.app;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* 商品详情页-商品详细信息
*
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
* @date 2021/8/8
*/
@Data
@ApiModel("商品详情")
public class GoodsDetailVO {
@ApiModelProperty("商品基本信息")
private GoodsInfo goodsInfo;
@ApiModelProperty("商品属性列表")
private List<Attribute> attributeList;
@ApiModelProperty("商品规格列表")
private List<Specification> specList;
@ApiModelProperty("商品库存单元列表")
private List<Sku> skuList;
@Data
@ApiModel("商品信息")
public static class GoodsInfo {
@ApiModelProperty("商品ID")
private Long id;
@ApiModelProperty("商品名称")
private String name;
@ApiModelProperty("商品原价(单位:分)")
private Long originPrice;
@ApiModelProperty("商品零售价(单位:分)")
private Long price;
@ApiModelProperty("销量")
private Integer sales;
@ApiModelProperty("商品图册")
private List<String> album;
@ApiModelProperty("商品详情")
private String detail;
}
@Data
@ApiModel("属性信息")
public static class Attribute {
@ApiModelProperty("属性ID")
private Long id;
@ApiModelProperty("属性名称")
private String name;
@ApiModelProperty("属性值")
private String value;
}
@Data
@ApiModel("规格信息")
public static class Specification {
@ApiModelProperty(value = "规格名称", example = "颜色")
private String name;
@ApiModelProperty(value = "规格项列表", example = "[黑,白]")
private List<Value> values;
@Data
@ApiModel("规格项")
public static class Value {
@ApiModelProperty("规格项ID")
private Long id;
@ApiModelProperty("规格项值")
private String value;
}
}
@Data
@ApiModel("商品库存单元")
public static class Sku {
@ApiModelProperty("库存单元ID")
private Long id;
@ApiModelProperty("库存单元名称")
private String name;
@ApiModelProperty("库存单元规格值ID集合,以英文逗号拼接")
private String specIds;
@ApiModelProperty("价格")
private Long price;
@ApiModelProperty("库存")
private Integer stock;
@ApiModelProperty("商品图片URL")
private String picUrl;
}
}
package com.youlai.mall.pms.pojo.vo.app;
import lombok.Data;
/**
* 商品列表页-商品基础信息
*
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
* @date 2021/8/8
*/
@Data
public class GoodsVO {
private Long id;
private String name;
private Long price;
private Integer sales;
private String picUrl;
}
......@@ -4,8 +4,11 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.util.Arrays;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableTransactionManagement
......
......@@ -2,9 +2,10 @@ package com.youlai.mall.pms.config;
import com.google.common.base.Charsets;
import com.google.common.hash.Funnel;
import com.youlai.mall.pms.common.constant.PmsConstants;
import com.youlai.mall.pms.component.BloomRedisService;
import com.youlai.mall.pms.pojo.entity.PmsSpu;
import com.youlai.mall.pms.service.IProductService;
import com.youlai.mall.pms.serviceapp.IGoodsService;
import com.youlai.mall.pms.utils.BloomFilterUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
......@@ -13,11 +14,8 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import java.util.List;
import static com.youlai.mall.pms.common.constant.PmsConstants.PRODUCT_REDIS_BLOOM_FILTER;
/**
* @Author DaniR
* @Description
......@@ -28,7 +26,7 @@ import static com.youlai.mall.pms.common.constant.PmsConstants.PRODUCT_REDIS_BLO
@AllArgsConstructor
public class BloomFilterConfig implements InitializingBean {
private final IProductService iProductService;
private final IGoodsService goodsService;
private final RedisTemplate redisTemplate;
@Bean
......@@ -48,11 +46,11 @@ public class BloomFilterConfig implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
List<PmsSpu> list = iProductService.list();
List<PmsSpu> list = goodsService.list();
log.info("加载产品到布隆过滤器当中,size:{}", list.size());
if (!CollectionUtils.isEmpty(list)) {
list.stream().filter(item -> item.getId() > 0).forEach(item -> {
bloomRedisService().addByBloomFilter(PRODUCT_REDIS_BLOOM_FILTER, item.getId() + "");
bloomRedisService().addByBloomFilter(PmsConstants.PRODUCT_REDIS_BLOOM_FILTER, item.getId() + "");
});
}
}
......
......@@ -17,7 +17,7 @@ import java.util.List;
/**
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
*/
@Api(tags = "移动端-分类信息")
@Api(tags = "移动端-商品分类")
@RestController("appCategoryController")
@RequestMapping("/app-api/v1/categories")
@Slf4j
......
package com.youlai.mall.pms.controller.app;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.common.result.Result;
import com.youlai.mall.pms.pojo.dto.app.SkuDTO;
import com.youlai.mall.pms.pojo.dto.app.SkuLockDTO;
import com.youlai.mall.pms.service.IPmsSkuService;
import com.youlai.mall.pms.pojo.entity.PmsSpu;
import com.youlai.mall.pms.pojo.vo.app.GoodsDetailVO;
import com.youlai.mall.pms.pojo.vo.app.GoodsVO;
import com.youlai.mall.pms.serviceapp.IGoodsService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import jodd.util.StringUtil;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
@Api(tags = "移动端-商品")
@RestController("appGoodsController")
@Api(tags = "移动端-商品信息")
@RestController(value = "appGoodsController")
@RequestMapping("/app-api/v1/goods")
@AllArgsConstructor
public class GoodsController {
private IPmsSkuService iPmsSkuService;
private IGoodsService goodsService;
@ApiOperation(value = "商品详情")
@ApiImplicitParam(name = "id", value = "商品ID", required = true, paramType = "path", dataType = "Long")
@GetMapping("/{id}")
public Result detail(@PathVariable Long id) {
SkuDTO sku = iPmsSkuService.getSkuById(id);
return Result.success(sku);
}
@ApiOperation(value = "商品分页列表")
@ApiImplicitParams({
@ApiImplicitParam(name = "page", value = "页码", example = "1", paramType = "query", dataType = "Long"),
@ApiImplicitParam(name = "limit", value = "每页数量", example = "10", paramType = "query", dataType = "Long"),
@ApiImplicitParam(name = "name", value = "商品名称", example = "华为P50", paramType = "query", dataType = "String"),
@ApiImplicitParam(name = "categoryId", value = "商品类目", example = "1", paramType = "query", dataType = "Long"),
@ApiImplicitParam(name = "orderBy", value = "排序字段", example = "price", paramType = "query", dataType = "Long"),
@ApiImplicitParam(name = "isAsc", value = "是否升序", example = "false", paramType = "query", dataType = "Boolean")
})
@GetMapping
public Result list(Integer page, Integer limit, String name, Long categoryId, String orderBy, Boolean isAsc) {
Page<PmsSpu> pageResult = goodsService.page(new Page<>(page, limit), new QueryWrapper<PmsSpu>()
.eq(categoryId != null, "category_id", categoryId)
.like(StrUtil.isNotBlank(name), "name", name)
.select("id", "name", "pic_url", "price", "sales")
.orderBy(StringUtil.isNotBlank(orderBy), isAsc, StrUtil.toUnderlineCase(orderBy))
);
@ApiOperation("获取商品的库存数量")
@ApiImplicitParam(name = "id", value = "商品ID", required = true, paramType = "path", dataType = "Long")
@GetMapping("/{id}/stock")
public Result<Integer> getStockById(@PathVariable Long id) {
Integer stock = iPmsSkuService.getStockById(id);
return Result.success(stock);
List<GoodsVO> list = pageResult.getRecords().stream()
.map(item -> {
GoodsVO goodsVO = new GoodsVO();
BeanUtil.copyProperties(item, goodsVO);
return goodsVO;
}).collect(Collectors.toList());
return Result.success(list, pageResult.getTotal());
}
@ApiOperation(value = "锁定库存")
@ApiImplicitParam(name = "list", value = "商品列表", required = true, paramType = "body", dataType = "SkuLockDTO")
@PutMapping("/stocks/_lock")
public Result<Boolean> lockStock(@RequestBody List<SkuLockDTO> list) {
boolean result = iPmsSkuService.lockStock(list);
return Result.judge(result);
}
@ApiOperation(value = "解锁库存")
@ApiImplicitParam(name = "orderToken", value = "订单令牌", required = true, paramType = "body", dataType = "String")
@PutMapping("/stocks/_unlock")
public Result<Boolean> unlockStock(String orderToken) {
boolean result = iPmsSkuService.unlockStock(orderToken);
return Result.judge(result);
}
@ApiOperation(value = "扣减库存")
@ApiImplicitParam(name = "orderToken", value = "订单令牌", required = true, paramType = "body", dataType = "String")
@PutMapping("/stocks/_deduct")
public Result<Boolean> deductStock(String orderToken) {
boolean result = iPmsSkuService.deductStock(orderToken);
return Result.judge(result);
@ApiOperation(value = "商品详情")
@ApiImplicitParam(name = "id", value = "商品ID", required = true, paramType = "path", dataType = "Long")
@GetMapping("/{id}")
public Result<GoodsDetailVO> detail(@PathVariable Long id) {
GoodsDetailVO goodsDetailVO = goodsService.getGoodsById(id);
return Result.success(goodsDetailVO);
}
}
package com.youlai.mall.pms.controller.app;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.common.result.Result;
import com.youlai.mall.pms.pojo.dto.app.ProductFormDTO;
import com.youlai.mall.pms.pojo.entity.PmsSpu;
import com.youlai.mall.pms.pojo.dto.app.ProductDTO;
import com.youlai.mall.pms.service.IPmsSpuService;
import com.youlai.mall.pms.service.IProductService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
@Api(tags = "移动端-产品")
@RestController("appSpuController")
@RequestMapping("/app-api/v1/products")
@AllArgsConstructor
public class ProductController {
private IPmsSpuService iPmsSpuService;
private IProductService iProductService;
@ApiOperation(value = "列表分页")
@ApiImplicitParams({
@ApiImplicitParam(name = "page", value = "页码", defaultValue = "1", paramType = "query", dataType = "Long"),
@ApiImplicitParam(name = "limit", value = "每页数量", defaultValue = "10", paramType = "query", dataType = "Long"),
@ApiImplicitParam(name = "name", value = "商品名称", paramType = "query", dataType = "String"),
@ApiImplicitParam(name = "categoryId", value = "商品类目", paramType = "query", dataType = "Long")
})
@GetMapping
public Result list(
Integer page,
Integer limit,
String name,
Long categoryId
) {
Page<PmsSpu> result = iPmsSpuService.page(new Page<>(page, limit), new LambdaQueryWrapper<PmsSpu>()
.eq(categoryId != null, PmsSpu::getCategoryId, categoryId)
.like(StrUtil.isNotBlank(name), PmsSpu::getName, name)
.select(PmsSpu::getId,
PmsSpu::getName,
PmsSpu::getPicUrl,
PmsSpu::getPrice,
PmsSpu::getSales
)
);
List<ProductDTO> list = result.getRecords().stream()
.map(item -> JSONUtil.toBean(JSONUtil.toJsonStr(item), ProductDTO.class))
.collect(Collectors.toList());
return Result.success(list, result.getTotal());
}
@ApiOperation(value = "商品详情")
@ApiImplicitParam(name = "id", value = "商品ID", required = true, paramType = "path", dataType = "Long")
@GetMapping("/{id}")
public Result detail(@PathVariable Long id) {
ProductFormDTO product = iProductService.getProductById(id);
return Result.success(product);
}
}
package com.youlai.mall.pms.controller.app;
import com.youlai.common.result.Result;
import com.youlai.mall.pms.pojo.dto.app.SkuDTO;
import com.youlai.mall.pms.pojo.dto.app.LockStockDTO;
import com.youlai.mall.pms.service.IPmsSkuService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Api(tags = "移动端-商品库存")
@RestController(value = "appStockController")
@RequestMapping("/app-api/v1/stocks")
@AllArgsConstructor
public class StockController {
private IPmsSkuService iPmsSkuService;
@ApiOperation(value = "商品库存单元详情")
@GetMapping("/{skuId}")
public Result detail(@PathVariable Long skuId) {
SkuDTO sku = iPmsSkuService.getSkuById(skuId);
return Result.success(sku);
}
@ApiOperation("获取商品的库存数量")
@GetMapping("/{skuId}/stock")
public Result<Integer> getStockById(@PathVariable Long skuId) {
Integer stock = iPmsSkuService.getStockById(skuId);
return Result.success(stock);
}
@ApiOperation(value = "锁定库存")
@PutMapping("/_lock")
public Result<Boolean> lockStock(@RequestBody List<LockStockDTO> list) {
boolean result = iPmsSkuService.lockStock(list);
return Result.judge(result);
}
@ApiOperation(value = "解锁库存")
@PutMapping("/_unlock")
public Result<Boolean> unlockStock(String orderToken) {
boolean result = iPmsSkuService.unlockStock(orderToken);
return Result.judge(result);
}
@ApiOperation(value = "扣减库存")
@PutMapping("/_deduct")
public Result<Boolean> deductStock(String orderToken) {
boolean result = iPmsSkuService.deductStock(orderToken);
return Result.judge(result);
}
}
......@@ -17,10 +17,6 @@ public interface PmsSkuMapper extends BaseMapper<PmsSku> {
List<PmsSku> listBySpuId(Long spuId);
@Select("<script>" +
" select t1.id,t1.code,t1.name,t1.pic,t1.price,(t1.stock-t1.locked_stock) as stock,t2.name as spu_name from pms_sku t1" +
" left join pms_spu t2 on t1.spu_id=t2.id" +
" where t1.id=#{id}" +
"</script>")
SkuDTO getSkuById(Long id);
}
......@@ -6,6 +6,5 @@ import com.youlai.mall.pms.pojo.entity.PmsAttribute;
public interface IPmsAttributeService extends IService<PmsAttribute> {
boolean saveBatch(AttributeFormDTO attributeForm);
}
......@@ -3,7 +3,7 @@ package com.youlai.mall.pms.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.mall.pms.pojo.entity.PmsSku;
import com.youlai.mall.pms.pojo.dto.app.SkuDTO;
import com.youlai.mall.pms.pojo.dto.app.SkuLockDTO;
import com.youlai.mall.pms.pojo.dto.app.LockStockDTO;
import java.util.List;
......@@ -12,7 +12,7 @@ public interface IPmsSkuService extends IService<PmsSku> {
/**
* 锁定库存
*/
boolean lockStock(List<SkuLockDTO> list);
boolean lockStock(List<LockStockDTO> list);
/**
* 解锁库存
......
package com.youlai.mall.pms.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.mall.pms.pojo.dto.app.ProductFormDTO;
import com.youlai.mall.pms.pojo.entity.PmsSpu;
public interface IProductService extends IService<PmsSpu> {
ProductFormDTO getProductById(Long id);
}
......@@ -11,7 +11,7 @@ import com.youlai.common.web.exception.BizException;
import com.youlai.mall.pms.mapper.PmsSkuMapper;
import com.youlai.mall.pms.pojo.entity.PmsSku;
import com.youlai.mall.pms.pojo.dto.app.SkuDTO;
import com.youlai.mall.pms.pojo.dto.app.SkuLockDTO;
import com.youlai.mall.pms.pojo.dto.app.LockStockDTO;
import com.youlai.mall.pms.service.IPmsSkuService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
......@@ -39,7 +39,7 @@ public class PmsSkuServiceImpl extends ServiceImpl<PmsSkuMapper, PmsSku> impleme
* 创建订单时锁定库存
*/
@Override
public boolean lockStock(List<SkuLockDTO> skuLockList) {
public boolean lockStock(List<LockStockDTO> skuLockList) {
log.info("=======================创建订单,开始锁定商品库存=======================");
log.info("锁定商品信息:{}", skuLockList.toString());
if (CollectionUtil.isEmpty(skuLockList)) {
......@@ -64,17 +64,17 @@ public class PmsSkuServiceImpl extends ServiceImpl<PmsSkuMapper, PmsSku> impleme
});
// 锁定失败的商品集合
List<SkuLockDTO> unlockSkuList = skuLockList.stream().filter(item -> !item.getLocked()).collect(Collectors.toList());
List<LockStockDTO> unlockSkuList = skuLockList.stream().filter(item -> !item.getLocked()).collect(Collectors.toList());
if (CollectionUtil.isNotEmpty(unlockSkuList)) {
// 恢复已被锁定的库存
List<SkuLockDTO> lockSkuList = skuLockList.stream().filter(SkuLockDTO::getLocked).collect(Collectors.toList());
List<LockStockDTO> lockSkuList = skuLockList.stream().filter(LockStockDTO::getLocked).collect(Collectors.toList());
lockSkuList.forEach(item ->
this.update(new LambdaUpdateWrapper<PmsSku>()
.eq(PmsSku::getId, item.getSkuId())
.setSql("locked_stock = locked_stock - " + item.getCount()))
);
// 提示订单哪些商品库存不足
List<Long> ids = unlockSkuList.stream().map(SkuLockDTO::getSkuId).collect(Collectors.toList());
List<Long> ids = unlockSkuList.stream().map(LockStockDTO::getSkuId).collect(Collectors.toList());
throw new BizException("商品" + ids.toString() + "库存不足");
}
......@@ -96,7 +96,7 @@ public class PmsSkuServiceImpl extends ServiceImpl<PmsSkuMapper, PmsSku> impleme
return true;
}
List<SkuLockDTO> skuLockList = JSONUtil.toList(json, SkuLockDTO.class);
List<LockStockDTO> skuLockList = JSONUtil.toList(json, LockStockDTO.class);
skuLockList.forEach(item ->
this.update(new LambdaUpdateWrapper<PmsSku>()
......@@ -121,13 +121,13 @@ public class PmsSkuServiceImpl extends ServiceImpl<PmsSkuMapper, PmsSku> impleme
return true;
}
List<SkuLockDTO> skuLockList = JSONUtil.toList(json, SkuLockDTO.class);
List<LockStockDTO> skuLockList = JSONUtil.toList(json, LockStockDTO.class);
skuLockList.forEach(item -> {
boolean result = this.update(new LambdaUpdateWrapper<PmsSku>()
.eq(PmsSku::getId, item.getSkuId())
.setSql("inventory = inventory - " + item.getCount()) // 扣减库存
.setSql("locked_inventory = locked_inventory - " + item.getCount())
.setSql("stock = stock - " + item.getCount()) // 扣减库存
.setSql("locked_stock = locked_stock - " + item.getCount())
);
if (!result) {
throw new BizException("扣减库存失败,商品" + item.getSkuId() + "库存不足");
......
......@@ -32,6 +32,7 @@ import java.util.stream.Collectors;
/**
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
* @date 2021/08/08
*/
@Service
@RequiredArgsConstructor
......@@ -40,6 +41,14 @@ public class PmsSpuServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> impleme
private final IPmsSpuAttributeValueService iPmsSpuAttributeValueService;
private final BloomRedisService bloomRedisService;
/**
* 商品分页列表
*
* @param page
* @param name
* @param categoryId
* @return
*/
@Override
public IPage<PmsSpu> list(Page<PmsSpu> page, String name, Long categoryId) {
List<PmsSpu> list = this.baseMapper.list(page, name, categoryId);
......@@ -47,6 +56,12 @@ public class PmsSpuServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> impleme
return page;
}
/**
* 添加商品
*
* @param goods
* @return
*/
@Override
@Transactional
public boolean addGoods(GoodsFormDTO goods) {
......@@ -63,6 +78,12 @@ public class PmsSpuServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> impleme
}
/**
* 修改商品
*
* @param goods
* @return
*/
@Transactional
@Override
public boolean updateGoods(GoodsFormDTO goods) {
......@@ -85,6 +106,73 @@ public class PmsSpuServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> impleme
return saveResult;
}
/**
* 获取商品(SPU)详情
*
* @param id 商品(SPU)ID
* @return
*/
@Override
public GoodsDetailVO getGoodsById(Long id) {
GoodsDetailVO goodsDetailVO = new GoodsDetailVO();
PmsSpu spu = this.getById(id);
Assert.isTrue(spu != null, "商品不存在");
BeanUtil.copyProperties(spu, goodsDetailVO);
// 商品图册JSON字符串转集合
String album = spu.getAlbum();
if (StrUtil.isNotBlank(album)) {
List<String> picUrls = JSONUtil.toList(album, String.class);
goodsDetailVO.setSubPicUrls(picUrls);
}
// 商品属性列表
List<PmsSpuAttributeValue> attrList = iPmsSpuAttributeValueService.list(new LambdaQueryWrapper<PmsSpuAttributeValue>()
.eq(PmsSpuAttributeValue::getSpuId, id)
.eq(PmsSpuAttributeValue::getType, AttributeTypeEnum.ATTRIBUTE.getValue())
);
goodsDetailVO.setAttrList(attrList);
// 商品规格列表
List<PmsSpuAttributeValue> specList = iPmsSpuAttributeValueService.list(new LambdaQueryWrapper<PmsSpuAttributeValue>()
.eq(PmsSpuAttributeValue::getSpuId, id)
.eq(PmsSpuAttributeValue::getType, AttributeTypeEnum.SPECIFICATION.getValue())
);
goodsDetailVO.setSpecList(specList);
// 商品SKU列表
List<PmsSku> skuList = iPmsSkuService.list(new LambdaQueryWrapper<PmsSku>().eq(PmsSku::getSpuId, id));
goodsDetailVO.setSkuList(skuList);
return goodsDetailVO;
}
/**
* 批量删除商品(SPU)
*
* @param goodsIds
* @return
*/
@Override
@Transactional
public boolean removeByGoodsIds(List<Long> goodsIds) {
boolean result = true;
for (Long goodsId : goodsIds) {
// sku
iPmsSkuService.remove(new LambdaQueryWrapper<PmsSku>().eq(PmsSku::getSpuId, goodsId));
// 规格
iPmsSpuAttributeValueService.remove(new LambdaQueryWrapper<PmsSpuAttributeValue>().eq(PmsSpuAttributeValue::getId, goodsId));
// 属性
iPmsSpuAttributeValueService.remove(new LambdaQueryWrapper<PmsSpuAttributeValue>().eq(PmsSpuAttributeValue::getSpuId, goodsId));
// spu
result = this.removeById(goodsId);
}
return result;
}
/**
* 保存商品
*
......@@ -234,60 +322,4 @@ public class PmsSpuServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> impleme
}
return tempIdIdMap;
}
@Override
public GoodsDetailVO getGoodsById(Long id) {
GoodsDetailVO goodsDetailVO = new GoodsDetailVO();
PmsSpu spu = this.getById(id);
Assert.isTrue(spu != null, "商品不存在");
BeanUtil.copyProperties(spu, goodsDetailVO);
// 商品图册JSON字符串转JSON
String album = spu.getAlbum();
if (StrUtil.isNotBlank(album)) {
List<String> picUrls = JSONUtil.toList(album, String.class);
goodsDetailVO.setSubPicUrls(picUrls);
}
// 商品属性列表
List<PmsSpuAttributeValue> attrList = iPmsSpuAttributeValueService.list(new LambdaQueryWrapper<PmsSpuAttributeValue>()
.eq(PmsSpuAttributeValue::getSpuId, id)
.eq(PmsSpuAttributeValue::getType, AttributeTypeEnum.ATTRIBUTE.getValue())
);
goodsDetailVO.setAttrList(attrList);
// 商品规格列表
List<PmsSpuAttributeValue> specList = iPmsSpuAttributeValueService.list(new LambdaQueryWrapper<PmsSpuAttributeValue>()
.eq(PmsSpuAttributeValue::getSpuId, id)
.eq(PmsSpuAttributeValue::getType, AttributeTypeEnum.SPECIFICATION.getValue())
);
goodsDetailVO.setSpecList(specList);
// 商品SKU列表
List<PmsSku> skuList = iPmsSkuService.list(new LambdaQueryWrapper<PmsSku>().eq(PmsSku::getSpuId, id));
goodsDetailVO.setSkuList(skuList);
return goodsDetailVO;
}
@Override
public boolean removeByGoodsIds(List<Long> goodsIds) {
boolean result = true;
for (Long goodsId : goodsIds) {
// sku
iPmsSkuService.remove(new LambdaQueryWrapper<PmsSku>().eq(PmsSku::getSpuId, goodsId));
// 规格
iPmsSpuAttributeValueService.remove(new LambdaQueryWrapper<PmsSpuAttributeValue>().eq(PmsSpuAttributeValue::getId, goodsId));
// 属性
iPmsSpuAttributeValueService.remove(new LambdaQueryWrapper<PmsSpuAttributeValue>().eq(PmsSpuAttributeValue::getSpuId, goodsId));
// spu
result = this.removeById(goodsId);
}
return result;
}
}
package com.youlai.mall.pms.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.common.redis.utils.RedisUtils;
import com.youlai.mall.pms.config.ProductLocalCache;
import com.youlai.mall.pms.mapper.PmsSpuMapper;
import com.youlai.mall.pms.pojo.dto.app.ProductFormDTO;
import com.youlai.mall.pms.pojo.entity.PmsAttribute;
import com.youlai.mall.pms.pojo.entity.PmsSku;
import com.youlai.mall.pms.pojo.entity.PmsSpu;
import com.youlai.mall.pms.pojo.entity.PmsSpuAttributeValue;
import com.youlai.mall.pms.service.IPmsAttributeService;
import com.youlai.mall.pms.service.IPmsSkuService;
import com.youlai.mall.pms.service.IPmsSpuAttributeValueService;
import com.youlai.mall.pms.service.IProductService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import java.util.List;
import static com.youlai.mall.pms.common.constant.PmsConstants.LOCK_PRODUCT_DETAIL;
import static com.youlai.mall.pms.common.constant.PmsConstants.PRODUCT_DETAIL_CACHE;
/**
* @author haoxr
* @date 2020-11-06
*/
@Service
@Slf4j
@AllArgsConstructor
public class ProductServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> implements IProductService {
private final IPmsSkuService iPmsSkuService;
private final IPmsSpuAttributeValueService iPmsSpuAttributeValueService;
private final IPmsAttributeService iPmsSpecService;
private final RedisUtils redisUtils;
private final RedissonClient redissonClient;
private final ProductLocalCache productLocalCache;
@Override
public ProductFormDTO getProductById(Long spuId) {
//1、一级本地缓存设置
ProductFormDTO product = productLocalCache.get(PRODUCT_DETAIL_CACHE + spuId);
/* if (null != product) {
log.info("get LocalCache product:" + product);
return product;
}
//2、二级缓存设置,Redis中获取商品详情信息
product = (ProductFormDTO) redisUtils.get(PRODUCT_DETAIL_CACHE + spuId);
if (null != product) {
log.info("get redis product:" + product);
return product;
}
//3、分布式锁,保证原子操作
RLock lock = redissonClient.getLock(LOCK_PRODUCT_DETAIL + spuId);
try {
if (lock.tryLock()) {
// spu
PmsSpu spu = this.getById(spuId);
SpuDTO SpuDTO = new SpuDTO();
BeanUtil.copyProperties(spu, SpuDTO);
if (StrUtil.isNotBlank(spu.getAlbum())) {
// spu专辑图片转换处理 json字符串 -> List
List<String> pics = JSONUtil.toList(JSONUtil.parseArray(spu.getAlbum()), String.class);
SpuDTO.setPics(pics);
}
// 属性
List<PmsSpuAttributeValue> attrs = iPmsSpuAttributeValueService.list(
new LambdaQueryWrapper<PmsSpuAttributeValue>(
).eq(PmsSpuAttributeValue::getSpuId, spuId)
);
// 规格
// List<PmsSpec> specs = iPmsSpecService.listBySpuId(spuId);
List<PmsAttribute> specs=null;
// sku
List<PmsSku> skuList = iPmsSkuService.list(new LambdaQueryWrapper<PmsSku>().eq(PmsSku::getSpuId, spuId));
// product = new ProductFormDTO(SpuDTO, attrs, specs, skuList);
product = new ProductFormDTO();
//TODO 4、需要判断商品是否是秒杀商品,根据秒杀信息更新商品秒杀相关信息
log.info("get db product:" + product);
redisUtils.set(PRODUCT_DETAIL_CACHE + spuId, product, 3600);
productLocalCache.setLocalCache(PRODUCT_DETAIL_CACHE + spuId,product);
} else {
log.info("get redis2 product:" + product);
product = (ProductFormDTO) redisUtils.get(PRODUCT_DETAIL_CACHE + spuId);
if (null!=product) {
productLocalCache.setLocalCache(PRODUCT_DETAIL_CACHE + spuId, product);
}
}
} finally {
if (lock.isLocked()) {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}*/
return product;
}
}
package com.youlai.mall.pms.serviceapp;
import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.mall.pms.pojo.entity.PmsSpu;
import com.youlai.mall.pms.pojo.vo.app.GoodsDetailVO;
/**
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
* @date 2021/8/8
*/
public interface IGoodsService extends IService<PmsSpu> {
GoodsDetailVO getGoodsById(Long id);
}
package com.youlai.mall.pms.serviceapp.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.mall.pms.common.enums.AttributeTypeEnum;
import com.youlai.mall.pms.mapper.PmsSpuMapper;
import com.youlai.mall.pms.pojo.entity.PmsSku;
import com.youlai.mall.pms.pojo.entity.PmsSpu;
import com.youlai.mall.pms.pojo.entity.PmsSpuAttributeValue;
import com.youlai.mall.pms.pojo.vo.app.GoodsDetailVO;
import com.youlai.mall.pms.service.IPmsSkuService;
import com.youlai.mall.pms.service.IPmsSpuAttributeValueService;
import com.youlai.mall.pms.serviceapp.IGoodsService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author <a href="mailto:xianrui0365@163.com">xianrui</a>
* @date 2021/8/8
*/
@Service
@RequiredArgsConstructor
public class GoodsServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> implements IGoodsService {
final IPmsSpuAttributeValueService spuAttributeValueService;
final IPmsSkuService skuService;
@Override
public GoodsDetailVO getGoodsById(Long goodsId) {
GoodsDetailVO goodsDetailVO = new GoodsDetailVO();
PmsSpu pmsSpu = this.baseMapper.selectById(goodsId);
Assert.isTrue(pmsSpu != null, "商品不存在");
// 商品基本信息
GoodsDetailVO.GoodsInfo goodsInfo = new GoodsDetailVO.GoodsInfo();
BeanUtil.copyProperties(pmsSpu, goodsInfo, "album");
List<String> album = new ArrayList<>();
if (StrUtil.isNotBlank(pmsSpu.getPicUrl())) {
album.add(pmsSpu.getPicUrl());
}
if (StrUtil.isNotBlank(pmsSpu.getAlbum())) {
album.addAll(JSONUtil.parseArray(pmsSpu.getAlbum()).toList(String.class));
goodsInfo.setAlbum(album);
}
goodsDetailVO.setGoodsInfo(goodsInfo);
// 商品属性列表
List<GoodsDetailVO.Attribute> attributeList = spuAttributeValueService.list(new LambdaQueryWrapper<PmsSpuAttributeValue>()
.eq(PmsSpuAttributeValue::getType, AttributeTypeEnum.ATTRIBUTE.getValue())
.eq(PmsSpuAttributeValue::getSpuId, goodsId)
.select(PmsSpuAttributeValue::getId, PmsSpuAttributeValue::getName, PmsSpuAttributeValue::getValue)
).stream().map(item -> {
GoodsDetailVO.Attribute attribute = new GoodsDetailVO.Attribute();
BeanUtil.copyProperties(item, attribute);
return attribute;
}).collect(Collectors.toList());
goodsDetailVO.setAttributeList(attributeList);
// 商品规格列表
List<PmsSpuAttributeValue> specSourceList = spuAttributeValueService.list(new LambdaQueryWrapper<PmsSpuAttributeValue>()
.eq(PmsSpuAttributeValue::getType, AttributeTypeEnum.SPECIFICATION.getValue())
.eq(PmsSpuAttributeValue::getSpuId, goodsId)
.select(PmsSpuAttributeValue::getId, PmsSpuAttributeValue::getName, PmsSpuAttributeValue::getValue)
);
List<GoodsDetailVO.Specification> specList = new ArrayList<>();
// 规格Map [key:"颜色",value:[{id:1,value:"黑"},{id:2,value:"白"}]]
Map<String, List<PmsSpuAttributeValue>> specValueMap = specSourceList.stream()
.collect(Collectors.groupingBy(item -> item.getName()));
for (Map.Entry<String, List<PmsSpuAttributeValue>> entry : specValueMap.entrySet()) {
String specName = entry.getKey();
List<PmsSpuAttributeValue> specValueSourceList = entry.getValue();
// 规格映射处理
GoodsDetailVO.Specification spec = new GoodsDetailVO.Specification();
spec.setName(specName);
if (CollectionUtil.isNotEmpty(specValueSourceList)) {
List<GoodsDetailVO.Specification.Value> specValueList = specValueSourceList.stream().map(item -> {
GoodsDetailVO.Specification.Value specValue = new GoodsDetailVO.Specification.Value();
specValue.setId(item.getId());
specValue.setValue(item.getValue());
return specValue;
}).collect(Collectors.toList());
spec.setValues(specValueList);
specList.add(spec);
}
}
goodsDetailVO.setSpecList(specList);
// 商品SKU列表
List<PmsSku> skuSourceList = skuService.list(new LambdaQueryWrapper<PmsSku>().eq(PmsSku::getSpuId, goodsId));
if (CollectionUtil.isNotEmpty(skuSourceList)) {
List<GoodsDetailVO.Sku> skuList = skuSourceList.stream().map(item -> {
GoodsDetailVO.Sku sku = new GoodsDetailVO.Sku();
BeanUtil.copyProperties(item, sku);
return sku;
}).collect(Collectors.toList());
goodsDetailVO.setSkuList(skuList);
}
return goodsDetailVO;
}
}
......@@ -5,23 +5,32 @@
<mapper namespace="com.youlai.mall.pms.mapper.PmsSkuMapper">
<resultMap id="BaseResultMap" type="com.youlai.mall.pms.pojo.entity.PmsSku">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="spuId" column="spu_id" jdbcType="BIGINT"/>
<result property="name" column="name" jdbcType="VARCHAR"/>
<result property="sn" column="sn" jdbcType="VARCHAR"/>
<result property="picUrl" column="pic_url" jdbcType="VARCHAR"/>
<result property="specIds" column="specs" jdbcType="VARCHAR"/>
<result property="price" column="price" jdbcType="BIGINT"/>
<result property="stock" column="stock" jdbcType="INTEGER"/>
<result property="lockedStock" column="locked_stock" jdbcType="INTEGER"/>
<result property="gmtCreate" column="gmt_create" jdbcType="TIMESTAMP"/>
<result property="gmtModified" column="gmt_modified" jdbcType="TIMESTAMP"/>
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="spuId" column="spu_id" jdbcType="BIGINT"/>
<result property="name" column="name" jdbcType="VARCHAR"/>
<result property="sn" column="sn" jdbcType="VARCHAR"/>
<result property="picUrl" column="pic_url" jdbcType="VARCHAR"/>
<result property="specIds" column="specs" jdbcType="VARCHAR"/>
<result property="price" column="price" jdbcType="BIGINT"/>
<result property="stock" column="stock" jdbcType="INTEGER"/>
<result property="lockedStock" column="locked_stock" jdbcType="INTEGER"/>
<result property="gmtCreate" column="gmt_create" jdbcType="TIMESTAMP"/>
<result property="gmtModified" column="gmt_modified" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
id,spu_id,name,
id
,spu_id,name,
sn,pic_url,specs,
origin_price,price,stock,
locked_stock,gmt_create,gmt_modified
</sql>
<select id="getSkuById" resultType="com.youlai.mall.pms.pojo.dto.app.SkuDTO">
select t1.id, t1.sn, t1.name, t1.pic_url, t1.price, (t1.stock - t1.locked_stock) as stock, t2.name as goodsName
from pms_sku t1
left join pms_spu t2 on t1.spu_id = t2.id
where t1.id = #{id}
</select>
</mapper>
......@@ -32,9 +32,16 @@
<result property="stock" column="stock" jdbcType="INTEGER"/>
<result property="lockedStock" column="locked_stock" jdbcType="INTEGER"/>
</collection>-->
<collection property="skuList" column="id" select="getSkuListBySpuId">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="name" column="name" jdbcType="VARCHAR"/>
<result property="sn" column="sn" jdbcType="VARCHAR"/>
<result property="picUrl" column="pic_url" jdbcType="VARCHAR"/>
<result property="specIds" column="spec_ids" jdbcType="VARCHAR"/>
<result property="price" column="price" jdbcType="BIGINT"/>
<result property="stock" column="stock" jdbcType="INTEGER"/>
<result property="lockedStock" column="locked_stock" jdbcType="INTEGER"/>
</collection>
</resultMap>
<sql id="Base_Column_List">
......@@ -46,28 +53,40 @@
status,gmt_create,gmt_modified
</sql>
<select id="list" resultMap="BaseResultMap">
SELECT t1.name,
t1.pic_url,
t3.NAME categoryName,
t4.NAME brandName,
t2.id skuId,
t2.NAME skuName,
t2.sn,
t2.pic_url skuPicUrl,
t2.spec_ids,
t2.price skuPrice,
t2.stock
SELECT
t1.id,
t1.name,
t1.pic_url,
t1.origin_price,
t1.price,
t1.sales,
t1.unit,
t1.detail,
t1.description,
t3.NAME categoryName,
t4.NAME brandName
FROM pms_spu t1
LEFT JOIN pms_sku t2 ON t1.id = t2.spu_id
LEFT JOIN pms_category t3 ON t1.category_id = t3.id
LEFT JOIN pms_brand t4 ON t1.brand_id = t4.id
LEFT JOIN pms_category t3 ON t1.category_id = t3.id
LEFT JOIN pms_brand t4 ON t1.brand_id = t4.id
WHERE 1 = 1
<if test="categoryId!=null">
AND t1.category_id =#{categoryId}
</if>
<if test='name!=null and name neq ""'>
AND t1.category_id =#{categoryId}
AND t1.name like concat('%',#{name},'%')
</if>
</select>
<select id="getSkuListBySpuId" resultType="com.youlai.mall.pms.pojo.entity.PmsSku">
SELECT id ,
NAME ,
sn,
pic_url ,
spec_ids,
price ,
stock
FROM pms_sku
WHERE spu_id = #{id}
</select>
</mapper>
......@@ -18,7 +18,7 @@ public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public Result handleIllegalArgumentException(IllegalArgumentException e) {
log.error("非法参数异常,异常原因:{}",e.getMessage(),e);
log.error("业务异常:{}",e.getMessage(),e);
return Result.failed(e.getMessage());
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册