买家端需要的获取商家主页和模糊搜索接口

This commit is contained in:
litianxiang
2026-05-11 16:40:47 +08:00
parent e1d57f7b37
commit 0c1b74ddc0
11 changed files with 263 additions and 13 deletions

View File

@@ -8,12 +8,16 @@ import com.aida.seller.module.designer.dto.DesignerAuditDTO;
import com.aida.seller.module.designer.dto.DesignerDTO;
import com.aida.seller.module.designer.entity.DesignerEntity;
import com.aida.seller.module.designer.service.DesignerService;
import com.aida.seller.module.designer.vo.DesignerSearchVO;
import com.aida.seller.module.designer.vo.DesignerShopVO;
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.*;
import java.util.List;
@Tag(name = "设计师入驻管理")
@RestController
@RequestMapping("/designer")
@@ -95,4 +99,20 @@ public class DesignerController {
designerService.deleteByUserId(userId);
return Response.success();
}
@Operation(summary = "搜索设计师", description = "根据关键词不区分大小写同时匹配店铺名称和所有者姓名返回设计师信息、最近5张商品封面图按updateTime倒序、商品总数")
@GetMapping("/search")
public Response<List<DesignerSearchVO>> search(
@Parameter(description = "关键词(同时匹配店铺名称和所有者姓名,不区分大小写)") @RequestParam String keyword) {
List<DesignerSearchVO> result = designerService.searchDesigners(keyword);
return Response.success(result);
}
@Operation(summary = "获取设计师店铺详情", description = "根据 sellerId即 卖家userId获取店铺公开信息供买家端店铺主页调用")
@GetMapping("/shop/{sellerId}")
public Response<DesignerShopVO> getShopDetail(
@Parameter(description = "设计师用户ID") @PathVariable Long sellerId) {
DesignerShopVO vo = designerService.getShopDetailBySellerId(sellerId);
return Response.success(vo);
}
}

View File

@@ -4,9 +4,13 @@ import com.aida.seller.module.designer.dto.DesignerApplyDTO;
import com.aida.seller.module.designer.dto.DesignerAuditDTO;
import com.aida.seller.module.designer.dto.DesignerDTO;
import com.aida.seller.module.designer.entity.DesignerEntity;
import com.aida.seller.module.designer.vo.DesignerSearchVO;
import com.aida.seller.module.designer.vo.DesignerShopVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
public interface DesignerService extends IService<DesignerEntity> {
/**
@@ -58,4 +62,20 @@ public interface DesignerService extends IService<DesignerEntity> {
* @param userId 用户ID
*/
void deleteByUserId(Long userId);
/**
* 模糊搜索设计师(不区分大小写),返回设计师信息及关联商品封面列表
*
* @param keyword 关键词(同时匹配店铺名称和所有者姓名,不区分大小写)
* @return 搜索结果列表每条包含设计师基础信息、最近5张商品封面图按updateTime倒序、商品总数
*/
List<DesignerSearchVO> searchDesigners(String keyword);
/**
* 根据 sellerId即 userId获取设计师店铺详情供买家端店铺主页调用
*
* @param sellerId 设计师用户ID
* @return 店铺详情
*/
DesignerShopVO getShopDetailBySellerId(Long sellerId);
}

View File

@@ -9,6 +9,8 @@ import com.aida.seller.module.designer.dto.DesignerDTO;
import com.aida.seller.module.designer.entity.DesignerEntity;
import com.aida.seller.module.designer.enums.DesignerApplyStatusEnum;
import com.aida.seller.module.designer.mapper.DesignerMapper;
import com.aida.seller.module.designer.vo.DesignerSearchVO;
import com.aida.seller.module.designer.vo.DesignerShopVO;
import com.aida.seller.module.listing.entity.ListingEntity;
import com.aida.seller.module.listing.entity.ListingImageEntity;
import com.aida.seller.module.listing.mapper.ListingImageMapper;
@@ -25,8 +27,10 @@ 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.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.time.LocalDateTime;
@@ -296,4 +300,95 @@ public class DesignerServiceImpl extends ServiceImpl<DesignerMapper, DesignerEnt
// 3. 逻辑删除设计师本人
this.removeById(sellerId);
}
@Override
public List<DesignerSearchVO> searchDesigners(String keyword) {
// Step 1: 构造设计师模糊查询条件,同时匹配店铺名称和所有者姓名,不区分大小写
LambdaQueryWrapper<DesignerEntity> designerQuery = new LambdaQueryWrapper<>();
if (StringUtils.hasText(keyword)) {
designerQuery.and(wrapper -> wrapper
.apply("LOWER(shop_name) LIKE LOWER({0})", "%" + keyword + "%")
.or()
.apply("LOWER(owner_name) LIKE LOWER({0})", "%" + keyword + "%")
);
}
List<DesignerEntity> designers = this.list(designerQuery);
if (designers.isEmpty()) {
return List.of();
}
// Step 2: 提取设计师的 userId 集合,用于后续按 userId 查询其关联商品
List<Long> userIds = designers.stream()
.map(DesignerEntity::getUserId)
.collect(Collectors.toList());
// Step 3: 查询所有匹配设计师关联的商品,按 updateTime 倒序
LambdaQueryWrapper<ListingEntity> listingQuery = new LambdaQueryWrapper<ListingEntity>()
.in(ListingEntity::getSellerId, userIds)
.orderByDesc(ListingEntity::getUpdateTime);
List<ListingEntity> listings = listingMapper.selectList(listingQuery);
if (listings.isEmpty()) {
return designers.stream().map(d -> buildSearchVO(d, List.of(), 0L))
.collect(Collectors.toList());
}
// Step 4: 按 sellerId 分组,统计每个设计师的商品总数
Map<Long, Long> listingCountMap = listings.stream()
.collect(Collectors.groupingBy(
ListingEntity::getSellerId,
Collectors.counting()
));
// Step 5: 按 sellerId 分组,便于后续取每个设计师的商品列表
Map<Long, List<ListingEntity>> listingsByDesigner = listings.stream()
.collect(Collectors.groupingBy(ListingEntity::getSellerId));
// Step 6: 组装每个设计师的搜索结果,最多取 5 个商品封面图
return designers.stream().map(d -> {
List<String> covers = listingsByDesigner
.getOrDefault(d.getId(), 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);
return buildSearchVO(d, covers, listingTotal);
}).collect(Collectors.toList());
}
private DesignerSearchVO buildSearchVO(DesignerEntity entity, List<String> covers, Long listingTotal) {
DesignerSearchVO vo = new DesignerSearchVO();
vo.setSellerId(entity.getUserId());
vo.setShopName(entity.getShopName());
vo.setOwnerName(entity.getOwnerName());
vo.setAvatar(minioUtil.processMinioResource(entity.getAvatar(), CommonConstants.MINIO_PATH_TIMEOUT));
vo.setCovers(covers);
vo.setListingTotal(listingTotal);
return vo;
}
@Override
public DesignerShopVO getShopDetailBySellerId(Long sellerId) {
DesignerEntity entity = this.getOne(
new LambdaQueryWrapper<DesignerEntity>()
.eq(DesignerEntity::getUserId, sellerId)
.last("LIMIT 1")
);
if (entity == null) {
throw new BusinessException("设计师不存在");
}
DesignerShopVO vo = new DesignerShopVO();
vo.setShopName(entity.getShopName());
vo.setAvatar(minioUtil.processMinioResource(entity.getAvatar(), CommonConstants.MINIO_PATH_TIMEOUT));
vo.setBrandBanner(minioUtil.processMinioResource(entity.getBrandBanner(), CommonConstants.MINIO_PATH_TIMEOUT));
vo.setOwnerName(entity.getOwnerName());
vo.setDescription(entity.getDescription());
vo.setSocialLinks(entity.getSocialLinks());
return vo;
}
}

View File

@@ -0,0 +1,36 @@
package com.aida.seller.module.designer.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.util.List;
/**
* 设计师搜索结果VO
*/
@Data
public class DesignerSearchVO implements Serializable {
private static final long serialVersionUID = 1L;
/** 用户ID */
@JsonSerialize(using = ToStringSerializer.class)
private Long sellerId;
/** 店铺名称 */
private String shopName;
/** 所有者全名 */
private String ownerName;
/** 店铺头像URL */
private String avatar;
/** 商品封面图列表最多5张按更新时间倒序 */
private List<String> covers;
/** 该设计师的商品总数 */
private Long listingTotal;
}

View File

@@ -0,0 +1,32 @@
package com.aida.seller.module.designer.vo;
import lombok.Data;
import java.io.Serializable;
/**
* 设计师店铺详情VO供买家端店铺主页调用
*/
@Data
public class DesignerShopVO implements Serializable {
private static final long serialVersionUID = 1L;
/** 店铺名称 */
private String shopName;
/** 店铺头像URL */
private String avatar;
/** 品牌 Banner URL */
private String brandBanner;
/** 所有者全名 */
private String ownerName;
/** 店铺简介 */
private String description;
/** 社交媒体链接JSON 字符串) */
private String socialLinks;
}

View File

@@ -83,4 +83,15 @@ public class ListingController {
boolean needPopup = listingService.checkPopupReminder(sellerId);
return Response.success(needPopup ? 1 : 0);
}
@Operation(summary = "获取店铺商品列表", description = "按 status=1、deleted=0、designFor 筛选,返回店铺已发布商品分页列表")
@GetMapping("/shop")
public Response<PageResponse<ListingPageVO>> getShopListings(
@Parameter(description = "设计师用户ID") @RequestParam Long sellerId,
@Parameter(description = "适用性别 female/male/all") @RequestParam String designFor,
@Parameter(description = "页码") @RequestParam(defaultValue = "1") int pageNum,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "10") int pageSize) {
IPage<ListingPageVO> page = listingService.getShopListings(sellerId, designFor, pageNum, pageSize);
return Response.success(PageResponse.success(page));
}
}

View File

@@ -11,7 +11,8 @@ import lombok.Getter;
public enum DesignForEnum {
MALE("male", "男性"),
FEMALE("female", "女性");
FEMALE("female", "女性"),
ALL("all", "全部");
private final String code;
private final String desc;

View File

@@ -71,4 +71,16 @@ public interface ListingService extends IService<ListingEntity> {
* @return true需要弹窗false不需要
*/
boolean checkPopupReminder(Long sellerId);
/**
* 获取店铺已发布商品列表,供买家端店铺主页调用
* <p>按 status=1、deleted=0、sellerId、designFor 筛选,按 updateTime 倒序</p>
*
* @param sellerId 设计师用户ID
* @param designFor 适用性别 female/maleall 表示不限制性别
* @param pageNum 页码
* @param pageSize 每页数量
* @return 分页商品列表
*/
IPage<ListingPageVO> getShopListings(Long sellerId, String designFor, int pageNum, int pageSize);
}

View File

@@ -1,6 +1,7 @@
package com.aida.seller.module.listing.service;
import com.aida.seller.common.constants.CommonConstants;
import com.aida.seller.common.context.UserContext;
import com.aida.seller.common.exception.BusinessException;
import com.aida.seller.module.listing.dto.*;
import com.aida.seller.module.listing.entity.ListingEntity;
@@ -298,4 +299,35 @@ public class ListingServiceImpl extends ServiceImpl<ListingMapper, ListingEntity
}
}
}
@Override
public IPage<ListingPageVO> getShopListings(Long sellerId, String designFor, int pageNum, int pageSize) {
Page<ListingEntity> pageParam = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<ListingEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ListingEntity::getSellerId, sellerId);
DesignForEnum designForEnum = DesignForEnum.of(designFor);
if (designForEnum == null) {
throw new BusinessException("designFor 只能为 female/male/all");
}
if (designForEnum != DesignForEnum.ALL) {
queryWrapper.eq(ListingEntity::getDesignFor, designForEnum.getCode());
}
queryWrapper.eq(ListingEntity::getStatus, 1);
queryWrapper.eq(ListingEntity::getDeleted, 0);
queryWrapper.orderByDesc(ListingEntity::getUpdateTime);
IPage<ListingEntity> page = this.page(pageParam, queryWrapper);
Page<ListingPageVO> result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
boolean loggedIn = UserContext.getUserId() != 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

@@ -29,15 +29,6 @@ public class ListingPageVO implements Serializable {
/** 价格 */
private BigDecimal price;
/** 销量 */
private Integer salesVolume;
/** 浏览量 */
private Integer viewCount;
/** 状态 */
private Integer status;
/** 创建时间(用于排序) */
private LocalDateTime createTime;
/** 修改时间 */
private LocalDateTime updateTime;
}

View File

@@ -17,7 +17,7 @@ mybatis-plus:
type-aliases-package: com.aida.seller.module.*.entity
global-config:
db-config:
id-type: auto
id-type: assign_id
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0