zjk
2026-09-29 70e7eace440e324c74fb7dfe2b30dc5ba5b67205
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package com.tcm.training.admin.app.search.service;
 
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.tcm.training.admin.app.search.vo.AppSearchHealthCourseVO;
import com.tcm.training.admin.app.search.vo.AppSearchHealthPopularizationVO;
import com.tcm.training.admin.app.search.vo.AppSearchResultVO;
import com.tcm.training.admin.live.entity.HealthPopularization;
import com.tcm.training.admin.live.mapper.HealthCourseMapper;
import com.tcm.training.admin.live.mapper.HealthPopularizationMapper;
import com.tcm.training.admin.live.vo.HealthCourseListRow;
import org.springframework.stereotype.Service;
 
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
/**
 * APP 首页搜索聚合服务。
 */
@Service
public class AppSearchService {
 
    private static final int STATUS_ENABLED = 1;
 
    private final HealthCourseMapper healthCourseMapper;
    private final HealthPopularizationMapper healthPopularizationMapper;
 
    /**
     * 注入 APP 搜索所需依赖。
     */
    public AppSearchService(HealthCourseMapper healthCourseMapper,
                            HealthPopularizationMapper healthPopularizationMapper) {
        this.healthCourseMapper = healthCourseMapper;
        this.healthPopularizationMapper = healthPopularizationMapper;
    }
 
    /**
     * 按关键词聚合搜索养生课程与健康科普。
     */
    public AppSearchResultVO search(String keyword) {
        String normalizedKeyword = trimToNull(keyword);
        AppSearchResultVO resultVO = new AppSearchResultVO();
        if (normalizedKeyword == null) {
            resultVO.setCourses(Collections.<AppSearchHealthCourseVO>emptyList());
            resultVO.setPopularizations(Collections.<AppSearchHealthPopularizationVO>emptyList());
            return resultVO;
        }
        resultVO.setCourses(toCourseList(healthCourseMapper.selectAppSearchCourses(normalizedKeyword)));
        resultVO.setPopularizations(toPopularizationList(listPopularizations(normalizedKeyword)));
        return resultVO;
    }
 
    /**
     * 查询命中的健康科普内容。
     */
    private List<HealthPopularization> listPopularizations(String keyword) {
        List<HealthPopularization> list = healthPopularizationMapper.selectList(new LambdaQueryWrapper<HealthPopularization>()
                .eq(HealthPopularization::getDeleted, 0)
                .eq(HealthPopularization::getStatus, STATUS_ENABLED)
                .and(wrapper -> wrapper
                        .like(HealthPopularization::getTitle, keyword)
                        .or()
                        .like(HealthPopularization::getContent, keyword)
                        .or()
                        .like(HealthPopularization::getCreatedBy, keyword))
                .orderByAsc(HealthPopularization::getSort)
                .orderByDesc(HealthPopularization::getId));
        return list == null ? Collections.<HealthPopularization>emptyList() : list;
    }
 
    /**
     * 将课程搜索结果转换为 APP 搜索页卡片响应。
     */
    private List<AppSearchHealthCourseVO> toCourseList(List<HealthCourseListRow> rows) {
        if (rows == null || rows.isEmpty()) {
            return Collections.emptyList();
        }
        List<AppSearchHealthCourseVO> result = new ArrayList<AppSearchHealthCourseVO>(rows.size());
        for (HealthCourseListRow row : rows) {
            AppSearchHealthCourseVO courseVO = new AppSearchHealthCourseVO();
            courseVO.setId(row.getId());
            courseVO.setTitle(trimToEmpty(row.getTitle()));
            courseVO.setCoverImage(trimToEmpty(row.getCoverImg()));
            courseVO.setLevelName(trimToEmpty(row.getLevelName()));
            courseVO.setDifficultyName(trimToEmpty(row.getDifficultyName()));
            courseVO.setSalePrice(defaultPrice(row.getSalePrice()));
            courseVO.setLearnUserCount(defaultNumber(row.getLearnUserCount()));
            courseVO.setStudyingCount(defaultNumber(row.getStudyingCount()));
            result.add(courseVO);
        }
        return result;
    }
 
    /**
     * 将健康科普搜索结果转换为 APP 搜索页卡片响应。
     */
    private List<AppSearchHealthPopularizationVO> toPopularizationList(List<HealthPopularization> items) {
        if (items == null || items.isEmpty()) {
            return Collections.emptyList();
        }
        List<AppSearchHealthPopularizationVO> result =
                new ArrayList<AppSearchHealthPopularizationVO>(items.size());
        for (HealthPopularization item : items) {
            AppSearchHealthPopularizationVO popularizationVO = new AppSearchHealthPopularizationVO();
            popularizationVO.setId(item.getId());
            popularizationVO.setTitle(trimToEmpty(item.getTitle()));
            popularizationVO.setCoverImage(trimToEmpty(item.getCoverImg()));
            popularizationVO.setContentType(item.getContentType());
            result.add(popularizationVO);
        }
        return result;
    }
 
    /**
     * 兜底空价格字段。
     */
    private BigDecimal defaultPrice(BigDecimal value) {
        return value == null ? BigDecimal.ZERO : value;
    }
 
    /**
     * 兜底空数字字段。
     */
    private Integer defaultNumber(Integer value) {
        return value == null ? 0 : value;
    }
 
    /**
     * 将可空文本转换为空字符串。
     */
    private String trimToEmpty(String value) {
        if (value == null) {
            return "";
        }
        String trimmed = value.trim();
        return trimmed.isEmpty() ? "" : trimmed;
    }
 
    /**
     * 将可空关键字转换为可搜索文本。
     */
    private String trimToNull(String value) {
        if (value == null) {
            return null;
        }
        String trimmed = value.trim();
        return trimmed.isEmpty() ? null : trimmed;
    }
}