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);
/**
* 按 faceId 和 type 分组查询源素材,每组返回最新的一条记录
* @param sourceReqQuery 查询参数
* @return 分组后的素材列表
*/
List<SourceRespVO> queryGroupedByFaceAndType(SourceReqQuery sourceReqQuery);
SourceEntity querySameVideo(Long faceSampleId, Long deviceId);
int hasRelationTo(Long memberId, Long sourceId, int type);

View File

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

@@ -265,15 +265,58 @@
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
from member_source ms
left join source so on ms.source_id = so.id
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="type!=null">and ms.type = #{type} </if>
<if test="scenicId!= null">and ms.scenic_id = #{scenicId} </if>
<if test="isBuy!=null">and ms.is_buy = #{isBuy}</if>
order by so.create_time desc
</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 *
from source