diff --git a/src/main/java/com/aida/buyer/module/cart/controller/CartController.java b/src/main/java/com/aida/buyer/module/cart/controller/CartController.java new file mode 100644 index 0000000..0153c37 --- /dev/null +++ b/src/main/java/com/aida/buyer/module/cart/controller/CartController.java @@ -0,0 +1,52 @@ +package com.aida.buyer.module.cart.controller; + +import com.aida.buyer.common.context.UserContext; +import com.aida.buyer.module.cart.dto.AddCartRequest; +import com.aida.buyer.module.cart.dto.CartItemDTO; +import com.aida.buyer.module.cart.service.ICartService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/buyer/cart") +@RequiredArgsConstructor +@Tag(name = "Shopping Cart", description = "Buyer shopping cart management") +public class CartController { + + private final ICartService cartService; + + @Operation(summary = "加购", description = "支持单个 listingId 或批量 listingIds") + @PostMapping("/add") + public void addItem(@Valid @RequestBody AddCartRequest request) { + Long buyerId = UserContext.getBuyerId(); + cartService.addItem(buyerId, request); + } + + @Operation(summary = "移除单个商品") + @DeleteMapping("/remove") + public void removeItem( + @Parameter(description = "商品ID") @RequestParam Long listingId) { + Long buyerId = UserContext.getBuyerId(); + cartService.removeItem(buyerId, listingId); + } + + @Operation(summary = "清空购物车") + @DeleteMapping("/clear") + public void clearCart() { + Long buyerId = UserContext.getBuyerId(); + cartService.clearCart(buyerId); + } + + @Operation(summary = "查询购物车列表", description = "返回含商品详情的购物车项列表") + @GetMapping("/list") + public List getCartItems() { + Long buyerId = UserContext.getBuyerId(); + return cartService.getCartItems(buyerId); + } +} diff --git a/src/main/java/com/aida/buyer/module/cart/dto/AddCartRequest.java b/src/main/java/com/aida/buyer/module/cart/dto/AddCartRequest.java new file mode 100644 index 0000000..f179878 --- /dev/null +++ b/src/main/java/com/aida/buyer/module/cart/dto/AddCartRequest.java @@ -0,0 +1,22 @@ +package com.aida.buyer.module.cart.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +@Data +@Schema(description = "加购请求") +public class AddCartRequest implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "商品ID(单个加购)") + private Long listingId; + + @Schema(description = "商品ID列表(批量加购)") + private List listingIds; +} diff --git a/src/main/java/com/aida/buyer/module/cart/dto/CartItemDTO.java b/src/main/java/com/aida/buyer/module/cart/dto/CartItemDTO.java new file mode 100644 index 0000000..3c7dfd5 --- /dev/null +++ b/src/main/java/com/aida/buyer/module/cart/dto/CartItemDTO.java @@ -0,0 +1,36 @@ +package com.aida.buyer.module.cart.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +@Schema(description = "购物车项(含商品详情)") +public class CartItemDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "购物车记录ID") + private Long cartId; + + @Schema(description = "商品ID") + private Long listingId; + + @Schema(description = "商品标题") + private String title; + + @Schema(description = "商品封面图") + private String cover; + + @Schema(description = "商品价格") + private BigDecimal price; + + @Schema(description = "商品状态") + private Integer status; + + @Schema(description = "加入购物车时间") + private LocalDateTime addTime; +} diff --git a/src/main/java/com/aida/buyer/module/cart/entity/CartEntity.java b/src/main/java/com/aida/buyer/module/cart/entity/CartEntity.java new file mode 100644 index 0000000..d6b9ea9 --- /dev/null +++ b/src/main/java/com/aida/buyer/module/cart/entity/CartEntity.java @@ -0,0 +1,32 @@ +package com.aida.buyer.module.cart.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Data +@TableName(value = "buyer_cart", autoResultMap = true) +public class CartEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(type = IdType.ASSIGN_ID) + @JsonSerialize(using = ToStringSerializer.class) + private Long id; + + private Long buyerId; + + private Long listingId; + + private LocalDateTime addTime; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; +} diff --git a/src/main/java/com/aida/buyer/module/cart/mapper/CartMapper.java b/src/main/java/com/aida/buyer/module/cart/mapper/CartMapper.java new file mode 100644 index 0000000..6356d3a --- /dev/null +++ b/src/main/java/com/aida/buyer/module/cart/mapper/CartMapper.java @@ -0,0 +1,9 @@ +package com.aida.buyer.module.cart.mapper; + +import com.aida.buyer.module.cart.entity.CartEntity; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface CartMapper extends BaseMapper { +} diff --git a/src/main/java/com/aida/buyer/module/cart/service/ICartService.java b/src/main/java/com/aida/buyer/module/cart/service/ICartService.java new file mode 100644 index 0000000..0fc84c9 --- /dev/null +++ b/src/main/java/com/aida/buyer/module/cart/service/ICartService.java @@ -0,0 +1,17 @@ +package com.aida.buyer.module.cart.service; + +import com.aida.buyer.module.cart.dto.AddCartRequest; +import com.aida.buyer.module.cart.dto.CartItemDTO; + +import java.util.List; + +public interface ICartService { + + void addItem(Long buyerId, AddCartRequest request); + + void removeItem(Long buyerId, Long listingId); + + void clearCart(Long buyerId); + + List getCartItems(Long buyerId); +} diff --git a/src/main/java/com/aida/buyer/module/cart/service/impl/CartServiceImpl.java b/src/main/java/com/aida/buyer/module/cart/service/impl/CartServiceImpl.java new file mode 100644 index 0000000..8d717a2 --- /dev/null +++ b/src/main/java/com/aida/buyer/module/cart/service/impl/CartServiceImpl.java @@ -0,0 +1,116 @@ +package com.aida.buyer.module.cart.service.impl; + +import com.aida.buyer.common.result.Response; +import com.aida.buyer.module.cart.dto.AddCartRequest; +import com.aida.buyer.module.cart.dto.CartItemDTO; +import com.aida.buyer.module.cart.entity.CartEntity; +import com.aida.buyer.module.cart.mapper.CartMapper; +import com.aida.buyer.module.cart.service.ICartService; +import com.aida.buyer.module.listing.feign.ListingFeignClient; +import com.aida.buyer.module.listing.vo.ListingMallVO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class CartServiceImpl implements ICartService { + + private final CartMapper cartMapper; + private final ListingFeignClient listingFeignClient; + + @Override + public void addItem(Long buyerId, AddCartRequest request) { + List listingIds = resolveListingIds(request); + if (CollectionUtils.isEmpty(listingIds)) { + return; + } + + for (Long listingId : listingIds) { + CartEntity existing = cartMapper.selectOne( + new LambdaQueryWrapper() + .eq(CartEntity::getBuyerId, buyerId) + .eq(CartEntity::getListingId, listingId)); + + if (existing != null) { + existing.setAddTime(LocalDateTime.now()); + cartMapper.updateById(existing); + } else { + CartEntity entity = new CartEntity(); + entity.setBuyerId(buyerId); + entity.setListingId(listingId); + entity.setAddTime(LocalDateTime.now()); + cartMapper.insert(entity); + } + } + } + + @Override + public void removeItem(Long buyerId, Long listingId) { + cartMapper.delete(new LambdaQueryWrapper() + .eq(CartEntity::getBuyerId, buyerId) + .eq(CartEntity::getListingId, listingId)); + } + + @Override + public void clearCart(Long buyerId) { + cartMapper.delete(new LambdaQueryWrapper() + .eq(CartEntity::getBuyerId, buyerId)); + } + + @Override + public List getCartItems(Long buyerId) { + List cartItems = cartMapper.selectList( + new LambdaQueryWrapper() + .eq(CartEntity::getBuyerId, buyerId) + .orderByDesc(CartEntity::getAddTime)); + if (CollectionUtils.isEmpty(cartItems)) { + return List.of(); + } + + List listingIds = cartItems.stream() + .map(CartEntity::getListingId) + .toList(); + + Response> response = listingFeignClient.getListingSummaries(listingIds); + List listings = response != null && response.getData() != null + ? response.getData() + : List.of(); + + Map listingMap = listings.stream() + .collect(Collectors.toMap(ListingMallVO::getId, Function.identity())); + + return cartItems.stream() + .filter(item -> listingMap.containsKey(item.getListingId())) + .map(item -> { + ListingMallVO listing = listingMap.get(item.getListingId()); + CartItemDTO dto = new CartItemDTO(); + dto.setCartId(item.getId()); + dto.setListingId(item.getListingId()); + dto.setTitle(listing.getTitle()); + dto.setCover(listing.getCover()); + dto.setPrice(listing.getPrice()); + dto.setStatus(listing.getStatus()); + dto.setAddTime(item.getAddTime()); + return dto; + }) + .toList(); + } + + private List resolveListingIds(AddCartRequest request) { + List ids = new ArrayList<>(); + if (request.getListingId() != null) { + ids.add(request.getListingId()); + } + if (!CollectionUtils.isEmpty(request.getListingIds())) { + ids.addAll(request.getListingIds()); + } + return ids; + } +} diff --git a/src/main/java/com/aida/buyer/module/listing/feign/ListingFeignClient.java b/src/main/java/com/aida/buyer/module/listing/feign/ListingFeignClient.java index afff78a..68b3eee 100644 --- a/src/main/java/com/aida/buyer/module/listing/feign/ListingFeignClient.java +++ b/src/main/java/com/aida/buyer/module/listing/feign/ListingFeignClient.java @@ -9,8 +9,11 @@ import com.aida.buyer.module.listing.vo.ListingPageVO; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; +import java.util.List; + /** * 商品服务 Feign Client */ @@ -29,4 +32,7 @@ public interface ListingFeignClient { @RequestParam String designFor, @RequestParam(defaultValue = "1") int pageNum, @RequestParam(defaultValue = "10") int pageSize); + + @PostMapping("/mall/batch") + Response> getListingSummaries(@RequestBody List listingIds); } diff --git a/src/main/java/com/aida/buyer/module/listing/vo/ListingMallVO.java b/src/main/java/com/aida/buyer/module/listing/vo/ListingMallVO.java index 439f6a8..89082ca 100644 --- a/src/main/java/com/aida/buyer/module/listing/vo/ListingMallVO.java +++ b/src/main/java/com/aida/buyer/module/listing/vo/ListingMallVO.java @@ -22,4 +22,6 @@ public class ListingMallVO implements Serializable { private String title; private BigDecimal price; + + private Integer status; } diff --git a/src/main/java/com/aida/buyer/module/order/service/impl/BuyerOrderServiceImpl.java b/src/main/java/com/aida/buyer/module/order/service/impl/BuyerOrderServiceImpl.java index cb190af..ad8d946 100644 --- a/src/main/java/com/aida/buyer/module/order/service/impl/BuyerOrderServiceImpl.java +++ b/src/main/java/com/aida/buyer/module/order/service/impl/BuyerOrderServiceImpl.java @@ -6,6 +6,8 @@ import com.aida.buyer.common.result.PageResponse; import com.aida.buyer.common.result.Response; import com.aida.buyer.module.account.entity.BuyerAccount; import com.aida.buyer.module.account.mapper.BuyerAccountMapper; +import com.aida.buyer.module.cart.entity.CartEntity; +import com.aida.buyer.module.cart.mapper.CartMapper; import com.aida.buyer.module.order.dto.AssetsDTO; import com.aida.buyer.module.order.dto.BuyerOrdersDTO; import com.aida.buyer.module.order.dto.CreateOrderDTO; @@ -14,6 +16,7 @@ import com.aida.buyer.module.order.service.IBuyerOrderService; import com.aida.buyer.module.order.vo.AssetsVO; import com.aida.buyer.module.order.vo.BuyerOrderVO; import com.aida.buyer.module.order.vo.CreateOrderResultVO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -25,6 +28,7 @@ public class BuyerOrderServiceImpl implements IBuyerOrderService { private final OrderFeignClient orderFeignClient; private final BuyerAccountMapper buyerAccountMapper; + private final CartMapper cartMapper; @Override public Response> getMyOrders(BuyerOrdersDTO dto) { @@ -48,6 +52,13 @@ public class BuyerOrderServiceImpl implements IBuyerOrderService { CreateOrderResultVO result = orderResp.getData(); // TODO:调用支付模块,传入buyerId,result.getOrderIds(),result.getTotalAmount() + + if (listingIds != null && !listingIds.isEmpty()) { + cartMapper.delete(new LambdaQueryWrapper() + .eq(CartEntity::getBuyerId, buyerId) + .in(CartEntity::getListingId, listingIds)); + } + return Response.success(); } diff --git a/src/main/java/com/aida/buyer/module/order/vo/CreateOrderResultVO.java b/src/main/java/com/aida/buyer/module/order/vo/CreateOrderResultVO.java index 3563228..262a5a8 100644 --- a/src/main/java/com/aida/buyer/module/order/vo/CreateOrderResultVO.java +++ b/src/main/java/com/aida/buyer/module/order/vo/CreateOrderResultVO.java @@ -18,4 +18,7 @@ public class CreateOrderResultVO implements Serializable { @Schema(description = "所有订单总金额(HK$)") private BigDecimal totalAmount; + + @Schema(description = "本次成功下单的未购买商品ID列表") + private List unpurchasedListingIds; } diff --git a/src/main/resources/db/schema.sql b/src/main/resources/db/schema.sql index 7b4c712..6cfd1c4 100644 --- a/src/main/resources/db/schema.sql +++ b/src/main/resources/db/schema.sql @@ -24,3 +24,15 @@ CREATE TABLE IF NOT EXISTS `buyer_account` ( UNIQUE KEY `uk_username` (`username`), INDEX `idx_deleted` (`deleted`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='买家账号表'; + +-- 创建 buyer_cart 表 +CREATE TABLE IF NOT EXISTS `buyer_cart` ( + `id` BIGINT NOT NULL COMMENT '主键ID' PRIMARY KEY, + `buyer_id` BIGINT NOT NULL COMMENT '买家ID', + `listing_id` BIGINT NOT NULL COMMENT '商品ID', + `add_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '加入购物车时间', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + UNIQUE KEY `uk_buyer_listing` (`buyer_id`, `listing_id`), + INDEX `idx_buyer_id` (`buyer_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='购物车表';