订单相关接口
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
package com.aida.seller.common.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 标记接口仅允许内部服务调用(Feign 远程调用)。
|
||||
* <p>
|
||||
* 被此注解标记的 Controller 方法会通过 AOP 拦截,
|
||||
* 仅放行携带了正确内部调用 Header 的请求,外部 HTTP 请求将被拒绝。
|
||||
*
|
||||
* @see com.aida.seller.common.aop.InternalOnlyAspect
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface InternalOnly {
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.aida.seller.common.aop;
|
||||
|
||||
import com.aida.seller.common.annotation.InternalOnly;
|
||||
import com.aida.seller.common.constants.CommonConstants;
|
||||
import com.aida.seller.common.exception.BusinessException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
/**
|
||||
* AOP 切面:校验 {@link InternalOnly} 标记的方法是否来自内部服务调用。
|
||||
* <p>
|
||||
* 内部调用(Feign)会携带 {@link CommonConstants#INTERNAL_CALL_HEADER} Header,
|
||||
* 外部直接 HTTP 请求则不携带,视为非法访问。
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class InternalOnlyAspect {
|
||||
|
||||
@Around("@annotation(com.aida.seller.common.annotation.InternalOnly)")
|
||||
public Object validateInternalCall(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (attributes == null) {
|
||||
throw new BusinessException("禁止外部直接访问此接口");
|
||||
}
|
||||
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
String internalCall = request.getHeader(CommonConstants.INTERNAL_CALL_HEADER);
|
||||
if (!CommonConstants.INTERNAL_CALL_VALUE.equals(internalCall)) {
|
||||
log.warn("Unauthorized external access attempt to internal-only endpoint: {}",
|
||||
((MethodSignature) joinPoint.getSignature()).getMethod().getName());
|
||||
throw new BusinessException("禁止外部直接访问此接口");
|
||||
}
|
||||
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,9 @@ public class CommonConstants {
|
||||
|
||||
public static final int TOKEN_EXPIRE_TIME = 7 * 24; // token 7 天过期(Hour)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 内部服务间调用的签名 Header,Feign 远程调用时携带,用于标识为内部可信调用
|
||||
*/
|
||||
public static final String INTERNAL_CALL_HEADER = "X-Internal-Call";
|
||||
public static final String INTERNAL_CALL_VALUE = "true";
|
||||
}
|
||||
@@ -84,7 +84,7 @@ public class ListingController {
|
||||
return Response.success(needPopup ? 1 : 0);
|
||||
}
|
||||
|
||||
@Operation(summary = "获取店铺商品列表", description = "按 status=1、deleted=0、designFor 筛选,返回店铺已发布商品分页列表")
|
||||
@Operation(summary = "获取店铺商品列表", description = "按返回店铺已发布商品分页列表")
|
||||
@GetMapping("/shop/seller")
|
||||
public Response<PageResponse<ListingPageVO>> getShopListings(
|
||||
@Parameter(description = "设计师用户ID") @RequestParam Long sellerId,
|
||||
|
||||
@@ -24,7 +24,7 @@ public class ListingMallController {
|
||||
|
||||
private final ListingMallService listingMallService;
|
||||
|
||||
@Operation(summary = "商城首页商品分页列表", description = "面向所有卖家,支持分类筛选、多字段排序、分页")
|
||||
@Operation(summary = "商城首页商品分页列表", description = "")
|
||||
@PostMapping("/mall")
|
||||
public PageResponse<ListingMallVO> getMallListings(
|
||||
@RequestBody ListingMallQueryDTO dto) {
|
||||
|
||||
@@ -1,25 +1,30 @@
|
||||
package com.aida.seller.module.order.controller;
|
||||
|
||||
import com.aida.seller.common.annotation.InternalOnly;
|
||||
import com.aida.seller.common.context.UserContext;
|
||||
import com.aida.seller.common.result.PageResponse;
|
||||
import com.aida.seller.common.result.Response;
|
||||
import com.aida.seller.module.order.dto.CreateOrderDTO;
|
||||
import com.aida.seller.module.order.dto.OrderListDTO;
|
||||
import com.aida.seller.module.order.service.OrderService;
|
||||
import com.aida.seller.module.order.vo.BuyerOrderVO;
|
||||
import com.aida.seller.module.order.vo.CreateOrderResultVO;
|
||||
import com.aida.seller.module.order.vo.OrderSummaryVO;
|
||||
import com.aida.seller.module.order.vo.OrderVO;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* My Orders - 订单管理控制器
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.aida.seller.module.order.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 创建订单请求参数
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "创建订单请求参数")
|
||||
public class CreateOrderDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "商品ID列表")
|
||||
private List<Long> listingIds;
|
||||
|
||||
@Schema(description = "买家ID")
|
||||
private Long buyerId;
|
||||
|
||||
@Schema(description = "买家账号")
|
||||
private String buyerUsername;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.aida.seller.module.order.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 批量修改订单状态请求参数
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "批量修改订单状态请求参数")
|
||||
public class UpdateOrderStatusDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "订单ID列表")
|
||||
private List<Long> orderIds;
|
||||
|
||||
@Schema(description = "目标状态:0-未支付,1-已支付,2-已取消")
|
||||
private Integer status;
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.aida.seller.module.order.service;
|
||||
|
||||
import com.aida.seller.module.order.dto.CreateOrderDTO;
|
||||
import com.aida.seller.module.order.dto.OrderListDTO;
|
||||
import com.aida.seller.module.order.dto.UpdateOrderStatusDTO;
|
||||
import com.aida.seller.module.order.vo.BuyerOrderVO;
|
||||
import com.aida.seller.module.order.vo.CreateOrderResultVO;
|
||||
import com.aida.seller.module.order.vo.OrderSummaryVO;
|
||||
import com.aida.seller.module.order.vo.OrderVO;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
@@ -13,6 +16,21 @@ import java.util.List;
|
||||
*/
|
||||
public interface OrderService {
|
||||
|
||||
/**
|
||||
* 创建订单(按卖家分组合并)
|
||||
*
|
||||
* @param dto 创建订单参数(包含商品ID列表、买家ID、买家账号)
|
||||
* @return 包含订单ID列表和总金额的结果对象
|
||||
*/
|
||||
CreateOrderResultVO createOrder(CreateOrderDTO dto);
|
||||
|
||||
/**
|
||||
* 批量修改订单状态
|
||||
*
|
||||
* @param dto 包含订单ID列表和目标状态
|
||||
*/
|
||||
void updateOrderStatus(UpdateOrderStatusDTO dto);
|
||||
|
||||
/**
|
||||
* 获取卖家订单数据总览
|
||||
*
|
||||
@@ -31,10 +49,13 @@ public interface OrderService {
|
||||
IPage<OrderVO> getOrderPage(OrderListDTO dto, Long sellerId);
|
||||
|
||||
/**
|
||||
* 根据买家ID查询订单列表(供远程调用)
|
||||
* 根据买家ID分页查询订单列表(供远程调用)
|
||||
*
|
||||
* @param buyerId 买家ID
|
||||
* @return 买家订单列表
|
||||
* @param page 页码
|
||||
* @param size 每页数量
|
||||
* @param status 订单状态筛选(可选)
|
||||
* @return 买家订单分页列表,按更新时间降序排列
|
||||
*/
|
||||
List<BuyerOrderVO> getOrdersByBuyerId(Long buyerId);
|
||||
IPage<BuyerOrderVO> getOrdersByBuyerId(Long buyerId, long page, long size, Integer status);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,33 @@
|
||||
package com.aida.seller.module.order.service;
|
||||
|
||||
import com.aida.seller.common.constants.CommonConstants;
|
||||
import com.aida.seller.common.exception.BusinessException;
|
||||
import com.aida.seller.module.designer.entity.DesignerEntity;
|
||||
import com.aida.seller.module.designer.mapper.DesignerMapper;
|
||||
import com.aida.seller.module.listing.entity.ListingEntity;
|
||||
import com.aida.seller.module.listing.mapper.ListingMapper;
|
||||
import com.aida.seller.module.order.dto.CreateOrderDTO;
|
||||
import com.aida.seller.module.order.dto.OrderListDTO;
|
||||
import com.aida.seller.module.order.dto.UpdateOrderStatusDTO;
|
||||
import com.aida.seller.module.order.entity.OrderInfoEntity;
|
||||
import com.aida.seller.module.order.entity.OrderItemEntity;
|
||||
import com.aida.seller.module.order.mapper.OrderInfoMapper;
|
||||
import com.aida.seller.module.order.mapper.OrderItemMapper;
|
||||
import com.aida.seller.module.order.vo.BuyerOrderItemVO;
|
||||
import com.aida.seller.module.order.vo.BuyerOrderVO;
|
||||
import com.aida.seller.module.order.vo.CreateOrderResultVO;
|
||||
import com.aida.seller.module.order.vo.OrderSummaryVO;
|
||||
import com.aida.seller.module.order.vo.OrderVO;
|
||||
import com.aida.seller.util.MinioUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
@@ -35,6 +46,8 @@ public class OrderServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
||||
|
||||
private final OrderItemMapper orderItemMapper;
|
||||
private final MinioUtil minioUtil;
|
||||
private final ListingMapper listingMapper;
|
||||
private final DesignerMapper designerMapper;
|
||||
|
||||
/**
|
||||
* 查询指定卖家的订单汇总数据
|
||||
@@ -127,17 +140,22 @@ public class OrderServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BuyerOrderVO> getOrdersByBuyerId(Long buyerId) {
|
||||
public IPage<BuyerOrderVO> getOrdersByBuyerId(Long buyerId, long page, long size, Integer status) {
|
||||
Page<OrderInfoEntity> pageParam = new Page<>(page, size);
|
||||
|
||||
LambdaQueryWrapper<OrderInfoEntity> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(OrderInfoEntity::getBuyerId, buyerId);
|
||||
if (status != null) {
|
||||
wrapper.eq(OrderInfoEntity::getStatus, status);
|
||||
}
|
||||
wrapper.orderByDesc(OrderInfoEntity::getUpdateTime);
|
||||
|
||||
List<OrderInfoEntity> orders = this.list(wrapper);
|
||||
if (orders.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
Page<OrderInfoEntity> orderPage = this.page(pageParam, wrapper);
|
||||
if (orderPage.getRecords().isEmpty()) {
|
||||
return new Page<>(page, size, 0);
|
||||
}
|
||||
|
||||
List<Long> orderIds = orders.stream()
|
||||
List<Long> orderIds = orderPage.getRecords().stream()
|
||||
.map(OrderInfoEntity::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
@@ -147,7 +165,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
||||
).stream()
|
||||
.collect(Collectors.groupingBy(OrderItemEntity::getOrderId));
|
||||
|
||||
return orders.stream().map(order -> {
|
||||
List<BuyerOrderVO> voList = orderPage.getRecords().stream().map(order -> {
|
||||
BuyerOrderVO vo = new BuyerOrderVO();
|
||||
vo.setOrderId(order.getId());
|
||||
vo.setUpdateTime(order.getUpdateTime());
|
||||
@@ -169,5 +187,114 @@ public class OrderServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
||||
|
||||
return vo;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
Page<BuyerOrderVO> resultPage = new Page<>(orderPage.getCurrent(), orderPage.getSize(), orderPage.getTotal());
|
||||
resultPage.setRecords(voList);
|
||||
return resultPage;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public CreateOrderResultVO createOrder(CreateOrderDTO dto) {
|
||||
if (CollectionUtils.isEmpty(dto.getListingIds())) {
|
||||
throw new BusinessException("商品ID列表不能为空");
|
||||
}
|
||||
if (dto.getBuyerId() == null) {
|
||||
throw new BusinessException("买家ID不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(dto.getBuyerUsername())) {
|
||||
throw new BusinessException("买家账号不能为空");
|
||||
}
|
||||
|
||||
List<ListingEntity> listings = listingMapper.selectBatchIds(dto.getListingIds());
|
||||
if (CollectionUtils.isEmpty(listings)) {
|
||||
throw new BusinessException("未找到对应的商品");
|
||||
}
|
||||
|
||||
Map<Long, List<ListingEntity>> listingsBySeller = listings.stream()
|
||||
.collect(Collectors.groupingBy(ListingEntity::getSellerId));
|
||||
|
||||
List<Long> orderIds = new ArrayList<>();
|
||||
List<BigDecimal> totalPrices = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<Long, List<ListingEntity>> entry : listingsBySeller.entrySet()) {
|
||||
Long sellerId = entry.getKey();
|
||||
List<ListingEntity> sellerListings = entry.getValue();
|
||||
|
||||
DesignerEntity designer = designerMapper.selectOne(
|
||||
new LambdaQueryWrapper<DesignerEntity>()
|
||||
.eq(DesignerEntity::getUserId, sellerId)
|
||||
.eq(DesignerEntity::getDeleted, 0));
|
||||
String shopName = (designer != null) ? designer.getShopName() : null;
|
||||
|
||||
BigDecimal totalPrice = sellerListings.stream()
|
||||
.map(ListingEntity::getPrice)
|
||||
.filter(p -> p != null)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
totalPrices.add(totalPrice);
|
||||
|
||||
Long totalViews = sellerListings.stream()
|
||||
.map(ListingEntity::getViewCount)
|
||||
.filter(v -> v != null)
|
||||
.reduce(0, Integer::sum).longValue();
|
||||
|
||||
for (ListingEntity listing : sellerListings) {
|
||||
listing.setSalesVolume(listing.getSalesVolume() == null ? 1 : listing.getSalesVolume() + 1);
|
||||
listingMapper.updateById(listing);
|
||||
}
|
||||
|
||||
OrderInfoEntity order = new OrderInfoEntity();
|
||||
order.setSellerId(sellerId);
|
||||
order.setBuyerId(dto.getBuyerId());
|
||||
order.setBuyerUsername(dto.getBuyerUsername());
|
||||
order.setStatus(0);
|
||||
order.setShopName(shopName);
|
||||
order.setTotalPrice(totalPrice);
|
||||
order.setTotalItems(sellerListings.size());
|
||||
order.setTotalViews(totalViews);
|
||||
this.save(order);
|
||||
|
||||
for (ListingEntity listing : sellerListings) {
|
||||
OrderItemEntity item = new OrderItemEntity();
|
||||
item.setOrderId(order.getId());
|
||||
item.setSellerId(sellerId);
|
||||
item.setBuyerId(dto.getBuyerId());
|
||||
item.setListingId(listing.getId());
|
||||
item.setListingName(listing.getTitle());
|
||||
item.setThumbnailUrl(listing.getCover());
|
||||
item.setPrice(listing.getPrice());
|
||||
item.setProductCategory(listing.getProductCategory());
|
||||
orderItemMapper.insert(item);
|
||||
}
|
||||
|
||||
orderIds.add(order.getId());
|
||||
}
|
||||
|
||||
CreateOrderResultVO result = new CreateOrderResultVO();
|
||||
result.setOrderIds(orderIds);
|
||||
BigDecimal grandTotal = totalPrices.stream()
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
result.setTotalAmount(grandTotal);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateOrderStatus(UpdateOrderStatusDTO dto) {
|
||||
if (dto == null || dto.getOrderIds() == null || dto.getOrderIds().isEmpty()) {
|
||||
throw new BusinessException("订单ID列表不能为空");
|
||||
}
|
||||
if (dto.getStatus() == null) {
|
||||
throw new BusinessException("订单状态不能为空");
|
||||
}
|
||||
|
||||
LambdaUpdateWrapper<OrderInfoEntity> updateWrapper = new LambdaUpdateWrapper<>();
|
||||
updateWrapper.in(OrderInfoEntity::getId, dto.getOrderIds())
|
||||
.set(OrderInfoEntity::getStatus, dto.getStatus());
|
||||
boolean updated = this.update(updateWrapper);
|
||||
if (!updated) {
|
||||
throw new BusinessException("订单不存在或无权修改");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.aida.seller.module.order.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Schema(description = "创建订单结果")
|
||||
public class CreateOrderResultVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "订单ID列表(按卖家分组)")
|
||||
private List<Long> orderIds;
|
||||
|
||||
@Schema(description = "所有订单总金额(HK$)")
|
||||
private BigDecimal totalAmount;
|
||||
}
|
||||
Reference in New Issue
Block a user