feat(service): 优化商品查询逻辑并新增分组查询接口

- 在 SourceMapper 中新增 queryGroupedByFaceAndType 方法,支持按 faceId 和 type 分组查询
- 调整 orderBiz.isBuy 方法的参数顺序,统一调用格式
- 修改 GoodsServiceImpl 中源素材查询逻辑,使用新分组方法减少循环嵌套
- 简化源素材去重及过滤禁用类型的处理流程
- 提前获取景区配置信息,避免重复查询
- 优化代码结构,提升可读性和维护性
This commit is contained in:
2025-11-21 21:43:37 +08:00
parent 91f3632e2b
commit 5b27cac6b0
3 changed files with 100 additions and 56 deletions

View File

@@ -67,6 +67,13 @@ public interface SourceMapper {
List<SourceRespVO> queryByRelation(SourceReqQuery sourceReqQuery); List<SourceRespVO> queryByRelation(SourceReqQuery sourceReqQuery);
/**
* 按 faceId 和 type 分组查询源素材,每组返回最新的一条记录
* @param sourceReqQuery 查询参数
* @return 分组后的素材列表
*/
List<SourceRespVO> queryGroupedByFaceAndType(SourceReqQuery sourceReqQuery);
SourceEntity querySameVideo(Long faceSampleId, Long deviceId); SourceEntity querySameVideo(Long faceSampleId, Long deviceId);
int hasRelationTo(Long memberId, Long sourceId, int type); int hasRelationTo(Long memberId, Long sourceId, int type);

View File

@@ -310,7 +310,7 @@ public class GoodsServiceImpl implements GoodsService {
goodsDetailVO.setFaceId(entity.getFaceId()); goodsDetailVO.setFaceId(entity.getFaceId());
goodsDetailVO.setIsBuy(entity.getIsBuy()); goodsDetailVO.setIsBuy(entity.getIsBuy());
if (Integer.valueOf(0).equals(entity.getIsBuy())) { if (Integer.valueOf(0).equals(entity.getIsBuy())) {
IsBuyRespVO buy = orderBiz.isBuy(userId, videoRespVO.getScenicId(), 0, videoId); IsBuyRespVO buy = orderBiz.isBuy(videoRespVO.getScenicId(), userId, entity.getFaceId(), 0, videoId);
if (!buy.isBuy()) { if (!buy.isBuy()) {
PriceObj priceObj = orderBiz.queryPrice(videoRespVO.getScenicId(), 0, videoId); PriceObj priceObj = orderBiz.queryPrice(videoRespVO.getScenicId(), 0, videoId);
if (priceObj.isFree()) { if (priceObj.isFree()) {
@@ -584,7 +584,7 @@ public class GoodsServiceImpl implements GoodsService {
goodsUrlVO.setCreateTime(source.getCreateTime()); goodsUrlVO.setCreateTime(source.getCreateTime());
return goodsUrlVO; return goodsUrlVO;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
IsBuyRespVO isBuy = orderBiz.isBuy(face.getMemberId(), face.getScenicId(), query.getSourceType(), face.getId()); IsBuyRespVO isBuy = orderBiz.isBuy(face.getScenicId(), face.getMemberId(), face.getId(), query.getSourceType(), face.getId());
if (!isBuy.isBuy()) { if (!isBuy.isBuy()) {
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(face.getScenicId()); ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(face.getScenicId());
if (scenicConfig != null && ((scenicConfig.getAntiScreenRecordType() & 2) == 0)) { if (scenicConfig != null && ((scenicConfig.getAntiScreenRecordType() & 2) == 0)) {
@@ -676,7 +676,7 @@ public class GoodsServiceImpl implements GoodsService {
} }
return true; return true;
}).count(); }).count();
IsBuyRespVO isBuy = orderBiz.isBuy(face.getMemberId(), face.getScenicId(), query.getSourceType(), face.getId()); IsBuyRespVO isBuy = orderBiz.isBuy(face.getScenicId(), face.getMemberId(), face.getId(), query.getSourceType(), face.getId());
if (count > 0) { if (count > 0) {
if (!isBuy.isBuy()) { if (!isBuy.isBuy()) {
return Collections.emptyList(); return Collections.emptyList();
@@ -842,17 +842,14 @@ public class GoodsServiceImpl implements GoodsService {
return ApiResponse.success(Collections.emptyList()); return ApiResponse.success(Collections.emptyList());
} }
// 获取景区配置
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId);
// 使用 LinkedHashMap 按 goodsType-goodsId 去重 // 使用 LinkedHashMap 按 goodsType-goodsId 去重
Map<String, GoodsPageVO> goodsMap = new LinkedHashMap<>(); Map<String, GoodsPageVO> goodsMap = new LinkedHashMap<>();
// 循环查询每个 faceId 的商品 // 循环查询每个 faceId 的商品
for (Long faceId : faceIds) { for (Long faceId : faceIds) {
// 构造查询参数
GoodsReqQuery query = new GoodsReqQuery();
query.setFaceId(faceId);
query.setIsBuy(isBuy);
query.setScenicId(scenicId);
// 查询成片 vlog (goodsType = 0) // 查询成片 vlog (goodsType = 0)
VideoReqQuery videoReqQuery = new VideoReqQuery(); VideoReqQuery videoReqQuery = new VideoReqQuery();
videoReqQuery.setScenicId(scenicId); videoReqQuery.setScenicId(scenicId);
@@ -880,55 +877,52 @@ public class GoodsServiceImpl implements GoodsService {
goodsMap.put(key, goodsPageVO); goodsMap.put(key, goodsPageVO);
} }
} }
}
// 查询源素材 (goodsType = 1/2) // 查询源素材 (goodsType = 1/2) - 使用新的 GROUP BY 方法
SourceReqQuery sourceReqQuery = new SourceReqQuery(); SourceReqQuery sourceReqQuery = new SourceReqQuery();
sourceReqQuery.setScenicId(scenicId); sourceReqQuery.setScenicId(scenicId);
sourceReqQuery.setIsBuy(isBuy); sourceReqQuery.setIsBuy(isBuy);
sourceReqQuery.setFaceId(faceId); sourceReqQuery.setFaceIds(faceIds);
List<SourceRespVO> sourceList = sourceMapper.queryByRelation(sourceReqQuery); // 使用 queryGroupedByFaceAndType 方法,数据库已经按 faceId+type 分组
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId); List<SourceRespVO> sourceList = sourceMapper.queryGroupedByFaceAndType(sourceReqQuery);
// faceIdtype 分组处理 // 遍历分组后的结果,每个 faceId+type 组合只有一条记录
sourceList.stream() for (SourceRespVO source : sourceList) {
.collect(Collectors.groupingBy(SourceRespVO::getFaceId)) Integer type = source.getType();
.forEach((sourceFaceId, goods) -> { Long sourceFaceId = source.getFaceId();
goods.stream()
.collect(Collectors.groupingBy(SourceRespVO::getType))
.forEach((type, sourcesByType) -> {
// 根据景区配置过滤禁用的素材类型
boolean isDisabled = false;
if (Integer.valueOf(1).equals(type)) {
isDisabled = Boolean.TRUE.equals(scenicConfig.getBoolean("disable_source_video"));
} else if (Integer.valueOf(2).equals(type)) {
isDisabled = Boolean.TRUE.equals(scenicConfig.getBoolean("disable_source_image"));
}
if (!isDisabled) { // 根据景区配置过滤禁用的素材类型
String key = type + "-" + sourceFaceId; // goodsType=type, goodsId=faceId(源素材用faceId作为ID) boolean isDisabled = false;
if (!goodsMap.containsKey(key)) { if (Integer.valueOf(1).equals(type)) {
GoodsPageVO goodsPageVO = new GoodsPageVO(); isDisabled = Boolean.TRUE.equals(scenicConfig.getBoolean("disable_source_video"));
goodsPageVO.setFaceId(sourceFaceId); } else if (Integer.valueOf(2).equals(type)) {
goodsPageVO.setGoodsType(type); isDisabled = Boolean.TRUE.equals(scenicConfig.getBoolean("disable_source_image"));
if (type == 1) { }
goodsPageVO.setGoodsName("录像集");
goodsPageVO.setTemplateCoverUrl(scenicConfig.getString("video_cover_url")); if (!isDisabled) {
} else if (type == 2) { String key = type + "-" + sourceFaceId; // goodsType=type, goodsId=faceId(源素材用faceId作为ID)
goodsPageVO.setGoodsName("照片集"); if (!goodsMap.containsKey(key)) {
goodsPageVO.setTemplateCoverUrl(scenicConfig.getString("photo_cover_url")); GoodsPageVO goodsPageVO = new GoodsPageVO();
} else { goodsPageVO.setFaceId(sourceFaceId);
goodsPageVO.setGoodsName("未知商品"); goodsPageVO.setGoodsType(type);
} if (type == 1) {
if (StringUtils.isBlank(goodsPageVO.getTemplateCoverUrl())) { goodsPageVO.setGoodsName("录像集");
goodsPageVO.setTemplateCoverUrl(sourcesByType.getFirst().getUrl()); goodsPageVO.setTemplateCoverUrl(scenicConfig.getString("video_cover_url"));
} } else if (type == 2) {
goodsPageVO.setScenicId(scenicId); goodsPageVO.setGoodsName("照片集");
goodsMap.put(key, goodsPageVO); goodsPageVO.setTemplateCoverUrl(scenicConfig.getString("photo_cover_url"));
} } else {
} goodsPageVO.setGoodsName("未知商品");
}); }
}); if (StringUtils.isBlank(goodsPageVO.getTemplateCoverUrl())) {
goodsPageVO.setTemplateCoverUrl(source.getUrl());
}
goodsPageVO.setScenicId(scenicId);
goodsMap.put(key, goodsPageVO);
}
}
} }
// 返回去重后的商品列表 // 返回去重后的商品列表

View File

@@ -267,13 +267,56 @@
left join source so on ms.source_id = so.id left join source so on ms.source_id = so.id
where where
ms.member_id = #{memberId} and so.id ms.member_id = #{memberId} and so.id is not null
<if test="faceId!= null">and ms.face_id = #{faceId} </if> <if test="faceId!= null">and ms.face_id = #{faceId} </if>
<if test="type!=null">and ms.type = #{type} </if> <if test="type!=null">and ms.type = #{type} </if>
<if test="scenicId!= null">and ms.scenic_id = #{scenicId} </if> <if test="scenicId!= null">and ms.scenic_id = #{scenicId} </if>
<if test="isBuy!=null">and ms.is_buy = #{isBuy}</if> <if test="isBuy!=null">and ms.is_buy = #{isBuy}</if>
order by so.create_time desc order by so.create_time desc
</select> </select>
<select id="queryGroupedByFaceAndType" resultType="com.ycwl.basic.model.pc.source.resp.SourceRespVO">
SELECT
t.id,
t.face_id,
t.scenic_id,
t.type,
t.thumb_url,
t.url,
t.is_free,
t.create_time,
t.update_time,
t.is_buy
FROM (
SELECT
so.id,
ms.face_id,
ms.scenic_id,
ms.type,
so.thumb_url,
so.url,
ms.is_free,
so.create_time,
so.update_time,
ms.is_buy,
ROW_NUMBER() OVER (PARTITION BY ms.face_id, ms.type ORDER BY so.create_time DESC) as rn
FROM member_source ms
LEFT JOIN source so ON ms.source_id = so.id
WHERE so.id IS NOT NULL
<if test="faceIds != null and faceIds.size() > 0">
AND ms.face_id IN
<foreach collection="faceIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
<if test="faceId != null">AND ms.face_id = #{faceId}</if>
<if test="scenicId != null">AND ms.scenic_id = #{scenicId}</if>
<if test="isBuy != null">AND ms.is_buy = #{isBuy}</if>
) t
WHERE t.rn = 1
ORDER BY t.create_time DESC
</select>
<select id="querySameVideo" resultType="com.ycwl.basic.model.pc.source.entity.SourceEntity"> <select id="querySameVideo" resultType="com.ycwl.basic.model.pc.source.entity.SourceEntity">
select * select *
from source from source