JavaWeb_LeadNews_Day7-ElasticSearch, Mongodb

elasticsearch

安装配置

https://blog.csdn.net/Y_cen/article/details/131856995

app文章搜索

创建索引库

  • 使用postman添加映射
    Put请求, Json格式 : http://192.168.174.133:9200/app_info_article
    {
        "mappings":{
            "properties":{
                "id":{
                    "type":"long"
                },
                "publishTime":{
                    "type":"date"
                },
                "layout":{
                    "type":"integer"
                },
                "images":{
                    "type":"keyword",
                    "index": false
                },
                "staticUrl":{
                    "type":"keyword",
                    "index": false
                },
                "authorId": {
                    "type": "long"
                },
                "authorName": {
                    "type": "text"
                },
                "title":{
                    "type":"text",
                    "analyzer":"ik_smart"
                },
                "content":{
                    "type":"text",
                    "analyzer":"ik_smart"
                }
            }
        }
    }
    
  • 依赖
    <!--elasticsearch-->
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
        <version>7.12.1</version>
    </dependency>
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-client</artifactId>
        <version>7.12.1</version>
    </dependency>
    <dependency>
        <groupId>org.elasticsearch</groupId>
        <artifactId>elasticsearch</artifactId>
        <version>7.12.1</version>
    </dependency>
    
  • 导入数据
    @Autowired
    private ApArticleMapper apArticleMapper;
    
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    
    /**
     * 注意:数据量的导入,如果数据量过大,需要分页导入
     * @throws Exception
     */
    @Test
    public void init() throws Exception {
        // 1. 查询所有符合条件的文章数据
        List<SearchArticleVo> searchArticleVos = apArticleMapper.loadArticleList();
        // 2. 批量导入到es索引库
        BulkRequest bulkRequest = new BulkRequest("app_info_article");
        for (SearchArticleVo searchArticleVo : searchArticleVos) {
            IndexRequest indexRequest = new IndexRequest().id(searchArticleVo.getId().toString()).source(JSON.toJSONString(searchArticleVo), XContentType.JSON);
            // 批量添加数据
            bulkRequest.add(indexRequest);
        }
        restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
    }
    

app文章搜索

思路分析

具体实现

  • 配置
    没有使用数据库, 所以无需数据源的自动配置
    spring:
        autoconfigure:
            exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
    elasticsearch:
        host: 192.168.174.133
        port: 9200
    
  • 业务代码
    @Service
    public class ArticleSearchServiceImpl implements ArticleSearchService {
    
        @Autowired
        private RestHighLevelClient restHighLevelClient;
    
        /**
         * es文章分页检索
         * @param dto
         * @return
         */
        @Override
        public ResponseResult search(UserSearchDto dto) throws IOException {
            // 1. 检查参数
            if(dto == null || StringUtils.isBlank(dto.getSearchWords())){
                return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
            }
            // 2. 设置查询条件
            SearchRequest searchRequest = new SearchRequest("app_info_article");
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    
            // 布尔查询
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    
            // 2.1 关键字的分词之后查询
            QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery(dto.getSearchWords()).field("title").field("content").defaultOperator(Operator.OR);
            boolQueryBuilder.must(queryStringQueryBuilder);
    
            // 2.2 查询小于mindate的数据
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("publishTime").lt(dto.getMinBehotTime().getTime());
            boolQueryBuilder.filter(rangeQueryBuilder);
    
            // 2.3 分页查询
            searchSourceBuilder.from(0);
            searchSourceBuilder.size(dto.getPageSize());
    
            // 2.4 按照发布时间倒序查询
            searchSourceBuilder.sort("publishTime", SortOrder.DESC);
    
            // 2.5 设置高亮 title
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("title");
            highlightBuilder.preTags("<font style='color: red; font-size: inherit;'>");
            highlightBuilder.postTags("</font>");
            searchSourceBuilder.highlighter(highlightBuilder);
    
            searchSourceBuilder.query(boolQueryBuilder);
            searchRequest.source(searchSourceBuilder);
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
    
            // 3. 结果封装返回
            List<Map> list = new ArrayList();
            SearchHit[] hits = searchResponse.getHits().getHits();
            for (SearchHit hit : hits) {
                String json = hit.getSourceAsString();
                Map map = JSON.parseObject(json, Map.class);
                // 处理高亮
                if(hit.getHighlightFields() != null && hit.getHighlightFields().size() > 0){
                    Text[] titles = hit.getHighlightFields().get("title").getFragments();
                    String title = StringUtils.join(titles);
                    // 高亮标题
                    map.put("h_title", title);
                }else{
                    // 原始标题
                    map.put("h_title", map.get("title"));
                }
                list.add(map);
            }
    
            return ResponseResult.okResult(list);
        }
    }
    
  • 网关配置
    app端网关
    #搜索微服务
    - id: leadnews-search
        uri: lb://leadnews-search
        predicates:
            - Path=/search/**
        filters:
            - StripPrefix= 1
    

新增文章创建索引

思路分析

具体实现

  • Producer
    SearchArticleVo需要静态地址, 所以需要在FreeMarker生成静态文章, 上传到MinIo之后再发送创建索引的消息
    @Service
    @Slf4j
    @Transactional
    public class ArticleFreemarkerServiceImpl implements ArticleFreemarkerService {
    
        ...
        @Override
        @Async
        public void buildArticleToMinIO(ApArticle article, String content) {
            if(StringUtils.isNotBlank(content)) {
                ...
    
                // kafka: 新增文章创建索引, 发送消息
                createArticleESIndex(article, content, path);
            }
        }
    
        @Autowired
        private KafkaTemplate<String, String> kafkaTemplate;
    
        private void createArticleESIndex(ApArticle article, String content, String path) {
    
            SearchArticleVo searchArticleVo = new SearchArticleVo();
            BeanUtils.copyProperties(article, searchArticleVo);
            searchArticleVo.setContent(content);
            searchArticleVo.setStaticUrl(path);
    
            kafkaTemplate.send(ArticleConstants.ARTICLE_ES_SYNC_TOPIC, JSON.toJSONString(searchArticleVo));
        }
    }
    
  • Listener
    @Component
    @Slf4j
    public class SyncArticleListener {
    
        @Autowired
        private RestHighLevelClient restHighLevelClient;
    
        @KafkaListener(topics = ArticleConstants.ARTICLE_ES_SYNC_TOPIC)
        public void onMessage(String message)
        {
            if(StringUtils.isNotBlank(message)){
    
                log.info("SyncArticleListener, message={}", message);
    
                SearchArticleVo vo = JSON.parseObject(message, SearchArticleVo.class);
                IndexRequest indexRequest = new IndexRequest("app_info_article");
                indexRequest.id(vo.getId().toString());
                indexRequest.source(message, XContentType.JSON);
                try {
                    restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
                } catch (IOException e) {
                    log.error("sync es error={}", e);
                    throw new RuntimeException(e);
                }
    
            }
        }
    
    }
    

MongoDB

安装配置

# 拉取镜像
docker pull mongo
# 创建容器
docker run -di --name mongo -p 27017:27017 -v ~/data/mongodata:/data mongo

SpringBoot集成MongoDB

  • 依赖
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-mongodb</artifactId>
    </dependency>
    
  • 配置
    spring:
        data:
            mongodb:
                host: 192.168.174.133
                port: 27017
                database: leadnews-history
    
  • 使用
    public class MongoTest {
    
    
        @Autowired
        private MongoTemplate mongoTemplate;
    
        //保存
        @Test
        public void saveTest(){
            ApAssociateWords apAssociateWords = new ApAssociateWords();
            apAssociateWords.setAssociateWords("黑马头条");
            apAssociateWords.setCreatedTime(new Date());
            mongoTemplate.save(apAssociateWords);
        }
    
        //查询一个
        @Test
        public void saveFindOne(){
            ApAssociateWords apAssociateWords = mongoTemplate.findById("64e46fd4f3a760442bf50527", ApAssociateWords.class);
            System.out.println(apAssociateWords);
        }
    
        //条件查询
        @Test
        public void testQuery(){
            Query query = Query.query(Criteria.where("associateWords").is("黑马头条"))
                    .with(Sort.by(Sort.Direction.DESC,"createdTime"));
            List<ApAssociateWords> apAssociateWordsList = mongoTemplate.find(query, ApAssociateWords.class);
            System.out.println(apAssociateWordsList);
        }
    
        @Test
        public void testDel(){
            mongoTemplate.remove(Query.query(Criteria.where("associateWords").is("黑马头条")),ApAssociateWords.class);
        }
    }
    

app文章搜索记录

保存搜索记录

思路分析

异步保存

保存数据

具体实现

public class ApUserSearchServiceImpl implements ApUserSearchService {

    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 保存用户搜索记录
     * @param keyword
     * @param userId
     */
    @Override
    @Async
    public void insert(String keyword, Integer userId) {
        // 1. 查询当前用户的搜索关键词
        Query query = Query.query(Criteria.where("userId").is(userId).and("keyword").is(keyword));
        ApUserSearch apUserSearch = mongoTemplate.findOne(query, ApUserSearch.class);

        // 2. 存在, 更新创建时间
        if(apUserSearch != null){
            apUserSearch.setCreatedTime(new Date());
            mongoTemplate.save(apUserSearch);
            return;
        }

        // 3. 不存在, 判断当前历史记录总数量是否超过10
        apUserSearch = new ApUserSearch();
        apUserSearch.setUserId(userId);
        apUserSearch.setKeyword(keyword);
        apUserSearch.setCreatedTime(new Date());

        Query query1 = Query.query(Criteria.where("userId").is(userId));
        query1.with(Sort.by(Sort.Direction.DESC, "createTime"));
        List<ApUserSearch> apUserSearchList = mongoTemplate.find(query1, ApUserSearch.class);

        if(apUserSearchList == null || apUserSearchList.size() < 10){
            mongoTemplate.save(apUserSearch);
        }else{
            ApUserSearch lastUserSearch = apUserSearchList.get(apUserSearchList.size() - 1);
            mongoTemplate.findAndReplace(Query.query(Criteria.where("id").is(lastUserSearch.getId())), apUserSearch);
        }
    }
}

在文章搜索中调用, 因为未登录也可能有token(游客), 所以需要判断是否登录.

ApUser user = AppThreadLocalUtil.getUser();
// 异步调用, 保存搜索记录
if(user!=null && dto.getFromIndex() == 0){
    apUserSearchService.insert(dto.getSearchWords(), user.getId());
}

查询搜索历史

public ResponseResult findUserSearch() {
    // 获取当前用户
    ApUser user = AppThreadLocalUtil.getUser();
    if(user == null){
        return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
    }
    // 根据用户查询数据, 根据时间排序
    Query query = Query.query(Criteria.where("userId").is(user.getId()));
    query.with(Sort.by(Sort.Direction.DESC, "createdTime"));
    List<ApUserSearch> apUserSearchList = mongoTemplate.find(query, ApUserSearch.class);
    return ResponseResult.okResult(apUserSearchList);
}

删除搜索历史

public ResponseResult delUserSearch(HistorySearchDto dto) {
    // 检查参数
    if(dto.getId() == null){
        return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
    }
    // 获取当前用户
    ApUser user = AppThreadLocalUtil.getUser();
    if(user == null){
        return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
    }
    // 删除
    Query query = Query.query(Criteria.where("id").is(dto.getId()).and("userId").is(user.getId()));
    mongoTemplate.remove(query, ApUserSearch.class);
    return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}

搜索联想词

public class ApAssociateWordsServiceImpl implements ApAssociateWordsService {

    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 搜索联想词
     * @param dto
     * @return
     */
    @Override
    public ResponseResult search(UserSearchDto dto) {
        // 1. 检查参数
        if(StringUtils.isBlank(dto.getSearchWords())){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        // 2. 分页检索
        if(dto.getPageSize() > 20){
            dto.setPageSize(20);
        }
        // 3. 执行查询, 模糊查询
        Query query = Query.query(Criteria.where("associateWords").regex(".*?\\" + dto.getSearchWords() + ".*"));
        query.limit(dto.getPageSize());
        List<ApAssociateWords> apAssociateWordsList = mongoTemplate.find(query, ApAssociateWords.class);
        return ResponseResult.okResult(apAssociateWordsList);
    }
}

来源

黑马程序员. 黑马头条

Gitee

https://gitee.com/yu-ba-ba-ba/leadnews

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
实现省市区三级联动的关键是建立好数据库,并且编写好对应的 SQL 语句。以下是一个简单的数据库设计: - 省份表 province,包括字段 id 和 name。 - 城市表 city,包括字段 id、name 和省份的外键 province_id。 - 区县表 district,包括字段 id、name 和城市的外键 city_id。 接下来是建表语句: ```sql -- 省份表 CREATE TABLE `province` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 城市表 CREATE TABLE `city` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL, `province_id` int(11) NOT NULL, PRIMARY KEY (`id`), KEY `province_id` (`province_id`), CONSTRAINT `city_ibfk_1` FOREIGN KEY (`province_id`) REFERENCES `province` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 区县表 CREATE TABLE `district` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL, `city_id` int(11) NOT NULL, PRIMARY KEY (`id`), KEY `city_id` (`city_id`), CONSTRAINT `district_ibfk_1` FOREIGN KEY (`city_id`) REFERENCES `city` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` 接下来是查询省份、城市、区县的 SQL 语句: ```sql -- 查询所有省份 SELECT id, name FROM province; -- 查询某个省份下的所有城市 SELECT id, name FROM city WHERE province_id = ?; -- 查询某个城市下的所有区县 SELECT id, name FROM district WHERE city_id = ?; ``` 在 Java Web 项目中,可以使用 Ajax 和 JSON 技术实现省市区三级联动。前端页面发送 Ajax 请求,后端通过 JDBC 连接数据库,查询对应的省份、城市、区县信息,并以 JSON 格式返回给前端页面。前端页面再解析 JSON 数据,更新页面的省份、城市、区县下拉框选项。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Y_cen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值