买家端接口fegin适配

This commit is contained in:
litianxiang
2026-05-18 14:54:08 +08:00
parent daf40ab224
commit a3020bccae
16 changed files with 380 additions and 17 deletions

View File

@@ -108,7 +108,7 @@ public class DesignerController {
return Response.success(result);
}
@Operation(summary = "获取设计师店铺详情", description = "根据 sellerId即 卖家userId获取店铺公开信息供买家端店铺主页调用")
@Operation(summary = "获取商城店铺详情", description = "根据 sellerId即 卖家userId获取店铺公开信息供买家端店铺主页调用")
@GetMapping("/shop/{sellerId}")
public Response<DesignerShopVO> getShopDetail(
@Parameter(description = "设计师用户ID") @PathVariable Long sellerId) {

View File

@@ -353,14 +353,14 @@ public class DesignerServiceImpl extends ServiceImpl<DesignerMapper, DesignerEnt
// Step 6: 组装每个设计师的搜索结果,最多取 5 个商品封面图
return designers.stream().map(d -> {
List<String> covers = listingsByDesigner
.getOrDefault(d.getId(), List.of())
.getOrDefault(d.getUserId(), List.of())
.stream()
.filter(l -> l.getCover() != null && !l.getCover().isBlank())
.limit(5)
.map(l -> minioUtil.processMinioResource(l.getCover(), CommonConstants.MINIO_PATH_TIMEOUT))
.collect(Collectors.toList());
long listingTotal = listingCountMap.getOrDefault(d.getId(), 0L);
long listingTotal = listingCountMap.getOrDefault(d.getUserId(), 0L);
return buildSearchVO(d, covers, listingTotal);
}).collect(Collectors.toList());
}

View File

@@ -40,7 +40,7 @@ public class ListingController {
public Response<ListingSaveDTO> getById(
@Parameter(description = "商品ID") @RequestParam Long id) {
Long sellerId = UserContext.getUserId();
ListingSaveDTO result = listingService.getById(id, sellerId);
ListingSaveDTO result = listingService.getById(id);
return Response.success(result);
}
@@ -85,7 +85,7 @@ public class ListingController {
}
@Operation(summary = "获取店铺商品列表", description = "按 status=1、deleted=0、designFor 筛选,返回店铺已发布商品分页列表")
@GetMapping("/shop")
@GetMapping("/shop/seller")
public Response<PageResponse<ListingPageVO>> getShopListings(
@Parameter(description = "设计师用户ID") @RequestParam Long sellerId,
@Parameter(description = "适用性别 female/male/all") @RequestParam String designFor,

View File

@@ -0,0 +1,42 @@
package com.aida.seller.module.listing.controller;
import com.aida.seller.common.result.PageResponse;
import com.aida.seller.common.result.Response;
import com.aida.seller.module.listing.dto.ListingMallQueryDTO;
import com.aida.seller.module.listing.service.ListingMallService;
import com.aida.seller.module.listing.vo.ListingDetailVO;
import com.aida.seller.module.listing.vo.ListingMallVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
* 商城首页商品 ControllerFeign 端)
*/
@Tag(name = "ListingMall - 商城首页商品")
@RestController
@RequestMapping("/listing")
@RequiredArgsConstructor
public class ListingMallController {
private final ListingMallService listingMallService;
@Operation(summary = "商城首页商品分页列表", description = "面向所有卖家,支持分类筛选、多字段排序、分页")
@PostMapping("/mall")
public PageResponse<ListingMallVO> getMallListings(
@RequestBody ListingMallQueryDTO dto) {
IPage<ListingMallVO> page = listingMallService.getMallListings(dto);
return PageResponse.success(page);
}
@Operation(summary = "商品详情(商城详情页)", description = "关联图片与店铺信息")
@GetMapping("/mall/detail")
public Response<ListingDetailVO> getListingDetail(
@Parameter(description = "商品ID") @RequestParam Long id) {
ListingDetailVO detail = listingMallService.getListingDetail(id);
return Response.success(detail);
}
}

View File

@@ -0,0 +1,34 @@
package com.aida.seller.module.listing.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 商城首页商品查询 DTO
*/
@Data
public class ListingMallQueryDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "适用性别 female/male/all不传表示全部")
private String designFor;
@Schema(description = "商品分类列表,支持多选,如 outwear&categories=dress")
private List<String> categories;
@Schema(description = "排序字段price/salesVolume/updateTime/viewCount/createTime默认 updateTime")
private String sortField = "updateTime";
@Schema(description = "排序方向asc/desc默认 desc")
private String sortOrder = "desc";
@Schema(description = "页码默认1")
private Integer pageNum = 1;
@Schema(description = "每页数量默认10")
private Integer pageSize = 10;
}

View File

@@ -0,0 +1,12 @@
package com.aida.seller.module.listing.mapper;
import com.aida.seller.module.listing.entity.ListingEntity;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 商城首页商品 Mapper
*/
@Mapper
public interface ListingMallMapper extends BaseMapper<ListingEntity> {
}

View File

@@ -0,0 +1,28 @@
package com.aida.seller.module.listing.service;
import com.aida.seller.module.listing.dto.ListingMallQueryDTO;
import com.aida.seller.module.listing.vo.ListingDetailVO;
import com.aida.seller.module.listing.vo.ListingMallVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
/**
* 商城首页商品 Service 接口
*/
public interface ListingMallService {
/**
* 商城首页商品分页列表
*
* @param dto 查询条件
* @return 分页结果
*/
IPage<ListingMallVO> getMallListings(ListingMallQueryDTO dto);
/**
* 获取商品详情(商城详情页)
*
* @param id 商品ID
* @return 商品详情
*/
ListingDetailVO getListingDetail(Long id);
}

View File

@@ -34,10 +34,9 @@ public interface ListingService extends IService<ListingEntity> {
* 获取商品详情(含所有图片)
*
* @param id 商品ID
* @param sellerId 卖家ID
* @return 商品详情
*/
ListingSaveDTO getById(Long id, Long sellerId);
ListingSaveDTO getById(Long id);
/**
* 分页查询商品列表

View File

@@ -110,11 +110,10 @@ public class ListingServiceImpl extends ServiceImpl<ListingMapper, ListingEntity
}
@Override
public ListingSaveDTO getById(Long id, Long sellerId) {
public ListingSaveDTO getById(Long id) {
ListingEntity entity = this.getOne(
new LambdaQueryWrapper<ListingEntity>()
.eq(ListingEntity::getId, id)
.eq(ListingEntity::getSellerId, sellerId)
.eq(ListingEntity::getDeleted, 0));
if (entity == null) {
throw new BusinessException("商品不存在");
@@ -318,14 +317,10 @@ public class ListingServiceImpl extends ServiceImpl<ListingMapper, ListingEntity
IPage<ListingEntity> page = this.page(pageParam, queryWrapper);
Page<ListingPageVO> result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
boolean loggedIn = UserContext.getBuyerId() != null;
result.setRecords(page.getRecords().stream().map(entity -> {
ListingPageVO vo = new ListingPageVO();
BeanUtils.copyProperties(entity, vo);
vo.setCover(minioUtil.processMinioResource(vo.getCover(), CommonConstants.MINIO_PATH_TIMEOUT));
if (!loggedIn) {
vo.setPrice(null);
}
return vo;
}).collect(Collectors.toList()));
return result;

View File

@@ -0,0 +1,167 @@
package com.aida.seller.module.listing.service.impl;
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.dto.ListingMallQueryDTO;
import com.aida.seller.module.listing.entity.ListingEntity;
import com.aida.seller.module.listing.entity.ListingImageEntity;
import com.aida.seller.module.listing.enums.DesignForEnum;
import com.aida.seller.module.listing.mapper.ListingImageMapper;
import com.aida.seller.module.listing.mapper.ListingMallMapper;
import com.aida.seller.module.listing.vo.ListingDetailVO;
import com.aida.seller.module.listing.vo.ListingMallVO;
import com.aida.seller.util.MinioUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 商城首页商品 Service 实现
*/
@Service
public class ListingMallServiceImpl extends ServiceImpl<ListingMallMapper, ListingEntity> implements com.aida.seller.module.listing.service.ListingMallService {
private final MinioUtil minioUtil;
private final ListingImageMapper listingImageMapper;
private final DesignerMapper designerMapper;
public ListingMallServiceImpl(MinioUtil minioUtil, ListingImageMapper listingImageMapper, DesignerMapper designerMapper) {
this.minioUtil = minioUtil;
this.listingImageMapper = listingImageMapper;
this.designerMapper = designerMapper;
}
@Override
public IPage<ListingMallVO> getMallListings(ListingMallQueryDTO dto) {
Page<ListingEntity> pageParam = new Page<>(dto.getPageNum(), dto.getPageSize());
LambdaQueryWrapper<ListingEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ListingEntity::getStatus, 1);
queryWrapper.eq(ListingEntity::getDeleted, 0);
if (dto.getDesignFor() != null && !dto.getDesignFor().isEmpty()) {
DesignForEnum designForEnum = DesignForEnum.of(dto.getDesignFor());
if (designForEnum != null && designForEnum != DesignForEnum.ALL) {
queryWrapper.eq(ListingEntity::getDesignFor, designForEnum.getCode());
}
}
if (!CollectionUtils.isEmpty(dto.getCategories())) {
for (String cat : dto.getCategories()) {
queryWrapper.apply(
"JSON_CONTAINS(product_category, {0}, '$')",
"\"" + cat + "\""
);
}
}
applySorting(queryWrapper, dto.getSortField(), dto.getSortOrder());
IPage<ListingEntity> page = this.page(pageParam, queryWrapper);
Page<ListingMallVO> result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
result.setRecords(page.getRecords().stream().map(entity -> {
ListingMallVO vo = new ListingMallVO();
vo.setId(entity.getId());
vo.setCover(minioUtil.processMinioResource(entity.getCover(), CommonConstants.MINIO_PATH_TIMEOUT));
vo.setTitle(entity.getTitle());
vo.setPrice(entity.getPrice());
return vo;
}).toList());
return result;
}
private void applySorting(LambdaQueryWrapper<ListingEntity> queryWrapper, String sortField, String sortOrder) {
if (!StringUtils.hasText(sortField)) {
queryWrapper.orderByDesc(ListingEntity::getUpdateTime);
return;
}
boolean isAsc = "asc".equalsIgnoreCase(sortOrder);
switch (sortField.toLowerCase()) {
case "price" -> {
if (isAsc) queryWrapper.orderByAsc(ListingEntity::getPrice);
else queryWrapper.orderByDesc(ListingEntity::getPrice);
}
case "salesvolume" -> {
if (isAsc) queryWrapper.orderByAsc(ListingEntity::getSalesVolume);
else queryWrapper.orderByDesc(ListingEntity::getSalesVolume);
}
case "viewcount" -> {
if (isAsc) queryWrapper.orderByAsc(ListingEntity::getViewCount);
else queryWrapper.orderByDesc(ListingEntity::getViewCount);
}
case "createtime" -> {
if (isAsc) queryWrapper.orderByAsc(ListingEntity::getCreateTime);
else queryWrapper.orderByDesc(ListingEntity::getCreateTime);
}
default -> queryWrapper.orderByDesc(ListingEntity::getUpdateTime);
}
}
@Override
public ListingDetailVO getListingDetail(Long id) {
ListingEntity entity = this.getOne(
new LambdaQueryWrapper<ListingEntity>()
.eq(ListingEntity::getId, id)
.eq(ListingEntity::getStatus, 1)
.eq(ListingEntity::getDeleted, 0));
if (entity == null) {
throw new BusinessException("商品不存在");
}
List<ListingImageEntity> images = listingImageMapper.selectList(
new LambdaQueryWrapper<ListingImageEntity>()
.eq(ListingImageEntity::getListingId, id)
.orderByAsc(ListingImageEntity::getSortOrder));
Map<String, List<String>> imageMap = images.stream()
.filter(img -> StringUtils.hasText(img.getCategory()))
// 先按 category 分组,再按 sortOrder 组内排序,确保同组图片按 sortOrder 升序排列
.sorted(Comparator.comparing(ListingImageEntity::getCategory)
.thenComparing(ListingImageEntity::getSortOrder, Comparator.nullsLast(Comparator.naturalOrder())))
.collect(Collectors.groupingBy(
ListingImageEntity::getCategory,
Collectors.mapping(
img -> minioUtil.processMinioResource(img.getImageUrl(), CommonConstants.MINIO_PATH_TIMEOUT),
Collectors.toList()
)
));
List<String> mainProductUrls = imageMap.get("main_product");
if (mainProductUrls != null && !mainProductUrls.isEmpty()) {
List<String> productUrls = imageMap.get("product");
if (productUrls != null) {
imageMap.put("product", productUrls.stream()
.filter(url -> !mainProductUrls.contains(url))
.toList());
}
}
DesignerEntity designer = designerMapper.selectOne(
new LambdaQueryWrapper<DesignerEntity>()
.eq(DesignerEntity::getUserId, entity.getSellerId())
.eq(DesignerEntity::getDeleted, 0));
ListingDetailVO vo = new ListingDetailVO();
vo.setId(entity.getId());
vo.setTitle(entity.getTitle());
vo.setDescription(entity.getDescription());
vo.setPrice(entity.getPrice());
vo.setUpdateTime(entity.getUpdateTime());
vo.setShopName(designer != null ? designer.getShopName() : null);
vo.setImages(imageMap);
return vo;
}
}

View File

@@ -0,0 +1,44 @@
package com.aida.seller.module.listing.vo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
* 商品详情 VO商城详情页
*/
@Data
@Schema(description = "商品详情")
public class ListingDetailVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "商品ID")
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@Schema(description = "商品标题")
private String title;
@Schema(description = "商品描述")
private String description;
@Schema(description = "价格")
private BigDecimal price;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
@Schema(description = "店铺名称")
private String shopName;
@Schema(description = "图片列表key 为 categoryvalue 为该分类下所有图片 URL")
private Map<String, List<String>> images;
}

View File

@@ -0,0 +1,25 @@
package com.aida.seller.module.listing.vo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 商城首页商品 VO
*/
@Data
public class ListingMallVO implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String cover;
private String title;
private BigDecimal price;
}

View File

@@ -25,6 +25,12 @@ public class OrderInfoEntity implements Serializable {
/** 卖家ID */
private Long sellerId;
/** 买家ID */
private Long buyerId;
/** 订单状态0-未支付1-已支付2-已取消 */
private Integer status;
/** 订单总金额HK$ */
private BigDecimal totalPrice;

View File

@@ -27,12 +27,18 @@ public class OrderItemEntity implements Serializable {
@JsonSerialize(using = ToStringSerializer.class)
private Long orderId;
/** 卖家ID */
private Long sellerId;
/** 买家ID */
private Long buyerId;
/** 商品ID */
@JsonSerialize(using = ToStringSerializer.class)
private Long listingId;
/** 商品名称 */
private String productName;
private String listingName;
/** 商品缩略图URL */
private String thumbnailUrl;

View File

@@ -82,6 +82,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
);
}
queryWrapper.eq(OrderInfoEntity::getStatus, 1);
queryWrapper.orderByDesc(OrderInfoEntity::getCreateTime);
Page<OrderInfoEntity> page = this.page(pageParam, queryWrapper);
@@ -109,7 +110,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
List<OrderVO.ItemVO> itemVOs = items.stream().map(item -> {
OrderVO.ItemVO itemVO = new OrderVO.ItemVO();
itemVO.setProductId(item.getListingId());
itemVO.setProductName(item.getProductName());
itemVO.setProductName(item.getListingName());
itemVO.setThumbnailUrl(minioUtil.processMinioResource(item.getThumbnailUrl(), CommonConstants.MINIO_PATH_TIMEOUT));
return itemVO;
}).collect(Collectors.toList());

View File

@@ -62,6 +62,8 @@ CREATE TABLE seller_designer (
CREATE TABLE seller_orders (
id BIGINT PRIMARY KEY COMMENT '主键ID',
seller_id BIGINT NOT NULL COMMENT '卖家ID',
buyer_id BIGINT NOT NULL COMMENT '买家ID',
status INT DEFAULT 0 COMMENT '订单状态: 0-未支付, 1-已支付, 2-已取消',
total_price DECIMAL(10,2) COMMENT '订单总金额(HK$)',
buyer_username VARCHAR(100) COMMENT '买家账号',
total_items INT COMMENT '商品总数量',
@@ -77,9 +79,11 @@ CREATE TABLE seller_orders (
-- 订单商品明细表
CREATE TABLE seller_order_item (
id BIGINT PRIMARY KEY COMMENT '主键ID',
order_id VARCHAR(50) NOT NULL COMMENT '订单ID',
order_id BIGINT NOT NULL COMMENT '订单ID',
seller_id BIGINT NOT NULL COMMENT '卖家ID',
buyer_id BIGINT NOT NULL COMMENT '买家ID',
listing_id BIGINT NOT NULL COMMENT '商品ID',
product_name VARCHAR(255) COMMENT '商品名称',
listing_name VARCHAR(255) COMMENT '商品名称',
thumbnail_url VARCHAR(200) COMMENT '商品缩略图URL',
price DECIMAL(10,2) COMMENT '成交单价(HK$)',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',