Spring + Boot + Cloud + JDK8 + Elasticsearch 单节点 模式下实现全文检索高亮-分页显示 快速入门案例

news2025/1/10 18:35:44

1. 安装elasticsearch+ik分词器插件

sudo wget https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-8.13.4.zip
sudo mkdir -p ./es_plugins/analysis-ik
sudo mkdir ./es_data
sudo unzip elasticsearch-analysis-ik-8.13.4.zip -d ./es_plugins/analysis-ik
sudo chown liber:liber es_data/ es_plugins/

1.1 docker-compose

version: '3.8'
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.13.4
    container_name: elasticsearch
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=true 
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    mem_limit: 1g  
    ports:
      - "9200:9200"
      - "9300:9300"
    networks:
      - esnet
    volumes:
      - ./es_data:/usr/share/elasticsearch/data
      - ./es_plugins:/usr/share/elasticsearch/plugins
networks:
  esnet:

启动容器命令:

docker-compose up -d

1.2 修改密码

进入容器

docker exec -it elasticsearch bash

验证分词器的安装:

bin/elasticsearch-plugin list

手动更改密码: 使用 Elasticsearch 提供的 elasticsearch-reset-password 工具重置 elastic 用户的密码:

bin/elasticsearch-reset-password -u elastic

1.3 可能会用到的指令

删除索引:

curl -u elastic:dxOHCIBIWu+2djY6qtF1 -X DELETE "http://127.0.0.1:9200/articles"

测试ik分词器:

 curl -X GET "http://127.0.0.1:9200/_analyze" -u elastic:dxOHCIBIWu+2djY6qtF1 -H 'Content-Type: application/json' -d'
 {
   "analyzer": "ik_max_word",
   "text": "Spring Boot 使用入门"
 }'

说明:dxOHCIBIWu+2djY6qtF1是密码。

2. 项目结构

2.1 完整的项目

在这里插入图片描述

说明:全局控制版本的依赖可以参考上一篇文章,本文只介绍artice-service模块。

2.2 artice-service模块

在这里插入图片描述

3. 数据库操作

create database if not exists blog;
use blog;
CREATE TABLE if not exists users
(
    id         BIGINT AUTO_INCREMENT PRIMARY KEY,                                          -- 用户的唯一标识符,自动递增的主键
    email      VARCHAR(100) NOT NULL UNIQUE,                                               -- 电子邮件,不能为空且唯一,长度限制为100个字符
    username   VARCHAR(12)  NOT NULL UNIQUE,                                               -- 用户名,不能为空且唯一,长度限制为12个字符
    password   VARCHAR(255) NOT NULL,                                                      -- 用户密码,不能为空,存储为加密后的字符串
    name       VARCHAR(50),                                                                -- 用户显示名称,非必填,长度限制为50个字符
    avatar_url VARCHAR(255),                                                               -- 用户头像的URL或文件路径,非必填,长度限制为255个字符
    role       VARCHAR(20)  NOT NULL DEFAULT 'USER',                                       -- 用户角色,不能为空,默认值为 'USER'
    enabled    BOOLEAN      NOT NULL DEFAULT TRUE,                                         -- 账户启用状态,不能为空,默认值为TRUE(启用)
    created_at DATETIME              DEFAULT CURRENT_TIMESTAMP,                            -- 记录创建时间,默认值为当前时间戳
    updated_at DATETIME              DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -- 记录最后更新时间,自动更新为当前时间戳
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci;

CREATE TABLE articles
(
    id         BIGINT AUTO_INCREMENT PRIMARY KEY,                               -- 主键,自增ID
    title      VARCHAR(255) NOT NULL,                                           -- 文章标题,非空
    content    TEXT         NOT NULL,                                           -- 文章内容 (Markdown格式),非空
    author     VARCHAR(100) NOT NULL,                                           -- 作者名称,非空
    user_id    BIGINT       NOT NULL,                                           -- 关联用户ID,外键
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,                             -- 创建时间,默认当前时间
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- 更新时间,默认当前时间,更新时自动更新
    FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE               -- 外键约束,用户删除时级联删除文章
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci; -- 使用utf8mb4字符集和Unicode排序规则

INSERT INTO articles (title, content, author, user_id)
VALUES ('Spring Boot 入门教程', '本文介绍如何使用 Spring Boot 快速创建一个应用程序。', '张三', 1),
       ('MyBatis 使用指南', '这篇文章将讲解 MyBatis 的基本使用方法。', '李四', 1),
       ('Elasticsearch 搜索引擎', '深入了解 Elasticsearch 的功能和用法。', '王五', 1),
       ('Java 并发编程', '本文详细讨论了 Java 中的并发编程技术。', '赵六', 1),
       ('Docker 容器化技术', '这篇文章介绍了如何使用 Docker 进行应用容器化。', '孙七', 1),
       ('微服务架构设计', '讨论了微服务架构的设计原则与实践。', '周八', 1),
       ('Redis 数据库应用', '本文详细介绍了 Redis 的常用操作与应用场景。', '吴九', 1),
       ('Spring Cloud 微服务', '使用 Spring Cloud 构建分布式系统。', '郑十', 1),
       ('RabbitMQ 消息队列', '探讨了 RabbitMQ 在消息队列中的应用。', '张三', 1),
       ('Kubernetes 集群管理', '介绍 Kubernetes 的基本概念与使用。', '李四', 1),
       ('RESTful API 设计', '探讨如何设计和实现 RESTful API。', '王五', 1),
       ('CI/CD 持续集成与部署', '本文介绍了 CI/CD 的概念和工具。', '赵六', 1),
       ('分布式系统概念', '讨论了分布式系统的核心概念。', '孙七', 1),
       ('大数据处理技术', '探讨了 Hadoop 和 Spark 在大数据处理中的应用。', '周八', 1),
       ('NoSQL 数据库应用', '介绍了 NoSQL 数据库的特点与应用场景。', '吴九', 1),
       ('前端开发框架 React', '详细介绍了 React 的基本概念与用法。', '郑十', 1),
       ('Vue.js 框架使用', '讨论了如何使用 Vue.js 构建用户界面。', '张三', 1),
       ('Angular 开发指南', '本文讲解了如何使用 Angular 进行开发。', '李四', 1),
       ('Git 版本控制系统', '介绍了 Git 的基本操作与分支管理。', '王五', 1)
       ('Agile 敏捷开发实践', '探讨了敏捷开发的原则与实践。', '赵六', 1);

4. pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>info.liberx</groupId>
        <artifactId>blog</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>article-service</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.4.6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.17.2</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>2.17.2</version>
        </dependency>

        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
    </dependencies>
</project>

5. application.yml

spring:
  application:
    name: article-service
  datasource:
    url: jdbc:mysql://localhost:3306/blog?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  data:
    redis:
      port: 6379
      host: 192.168.186.77
      password: 123456
      timeout: 10000
  jackson:
    serialization:
      write-dates-as-timestamps: false
elasticsearch:
  url: 192.168.186.77  # Elasticsearch 地址
  port: 9200
  username: elastic  # 如果有设置认证,提供用户名
  password: dxOHCIBIWu+2djY6qtF1  # 如果有设置认证,提供密码
mybatis:
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: true
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
    register-with-eureka: true
    fetch-registry: true
server:
  port: 8002

说明:Eurka中心,Redis在本案例暂时没有演示,需要Eurka中心是为了注册到服务中心通过Gateway网关进行转发,读者可以自行去掉一些不必要的依赖项还有配置项。

6. ElasticsearchConfig.java

package info.liberx.articleservice.config;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ElasticsearchConfig {
    @Value("${elasticsearch.url}")
    private String url;
    @Value("${elasticsearch.username}")
    private String username;
    @Value("${elasticsearch.password}")
    private String password;
    @Value("${elasticsearch.port}")
    private int port;
    @Bean
    public ElasticsearchClient elasticsearchClient() {
        // 配置 Jackson 的 ObjectMapper 并注册 JavaTimeModule
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());

        // 创建自定义的 JacksonJsonpMapper
        JacksonJsonpMapper jsonpMapper = new JacksonJsonpMapper(objectMapper);

        // 配置身份验证
        final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY,
                new UsernamePasswordCredentials(username, password));

        // 配置 RestClient
        RestClientBuilder builder = RestClient.builder(new HttpHost(url, port))
                .setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider));

        RestClient restClient = builder.build();

        // 使用 RestClientTransport 创建 ElasticsearchClient
        RestClientTransport transport = new RestClientTransport(
                restClient, jsonpMapper);

        return new ElasticsearchClient(transport);
    }
}

说明:注册了一个 JavaTimeModule,它使得 Jackson 能够正确处理 Java 8 的日期和时间 API。

7. ArticleController.java

package info.liberx.articleservice.controller;

import com.github.pagehelper.PageInfo;
import info.liberx.articleservice.model.Article;
import info.liberx.articleservice.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;

@RestController
@RequestMapping("/articles")
public class ArticleController {

    private final ArticleService articleService;

    @Autowired
    public ArticleController(ArticleService articleService) {
        this.articleService = articleService;
    }

    // 搜索文章
    @GetMapping("/search")
    public ResponseEntity<Page<Article>> searchArticles(
            @RequestParam String keyword,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size) {
        Pageable pageable = PageRequest.of(page, size);
        try {
            Page<Article> articles = articleService.searchArticles(keyword, pageable);
            return new ResponseEntity<>(articles, HttpStatus.OK);
        } catch (IOException e) {
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    // 创建新文章
    @PostMapping
    public ResponseEntity<Article> createArticle(@RequestBody Article article) {
        int result = articleService.createArticle(article);
        if (result > 0) {
            return new ResponseEntity<>(article, HttpStatus.CREATED);
        } else {
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        }
    }

    // 根据文章ID获取文章
    @GetMapping("/{id}")
    public ResponseEntity<Article> getArticleById(@PathVariable Long id) {
        Article article = articleService.getArticleById(id);
        if (article != null) {
            return new ResponseEntity<>(article, HttpStatus.OK);
        } else {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
    }

    // 更新文章
    @PutMapping("/{id}")
    public ResponseEntity<Article> updateArticle(@PathVariable Long id, @RequestBody Article article) {
        article.setId(id); // 确保更新的是指定ID的文章
        int result = articleService.updateArticle(article);
        if (result > 0) {
            return new ResponseEntity<>(article, HttpStatus.OK);
        } else {
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        }
    }

    // 删除文章
    @DeleteMapping("/{id}")
    public ResponseEntity<String> deleteArticle(@PathVariable Long id) {
        int result = articleService.deleteArticle(id);
        if (result > 0) {
            return new ResponseEntity<>("文章删除成功", HttpStatus.OK);
        } else {
            return new ResponseEntity<>("文章删除失败", HttpStatus.NOT_FOUND);
        }
    }

    // 获取所有文章
    // 获取所有文章,支持分页
    @GetMapping("/get/{userid}")
    public ResponseEntity<PageInfo<Article>> getAllArticles(
            @PathVariable Long userid,
            @RequestParam(defaultValue = "1") int page,  // 注意:PageHelper 的页码从1开始
            @RequestParam(defaultValue = "10") int size) {

        // 获取分页结果
        PageInfo<Article> articles = articleService.getArticlesByUserId(userid, page, size);
        return new ResponseEntity<>(articles, HttpStatus.OK);
    }
}

8. ArticleMapper.java

package info.liberx.articleservice.mapper;

import info.liberx.articleservice.model.Article;
import org.apache.ibatis.annotations.*;

import java.util.List;

@Mapper
public interface ArticleMapper {

    @Insert("INSERT INTO articles(title, content, author, user_id) VALUES(#{title}, #{content}, #{author}, #{userId})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insertArticle(Article article);

    @Select("SELECT * FROM articles WHERE id = #{id}")
    Article findArticleById(Long id);

    @Select("SELECT * FROM articles WHERE user_id = #{userId}")
    List<Article> findArticlesByUserId(Long userId);

    @Update("UPDATE articles SET title = #{title}, content = #{content}, author = #{author} WHERE id = #{id}")
    int updateArticle(Article article);

    @Delete("DELETE FROM articles WHERE id = #{id}")
    int deleteArticle(Long id);

    @Select("SELECT * FROM articles")
    List<Article> findAllArticles();
}

9. Article.java

package info.liberx.articleservice.model;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.time.OffsetDateTime;

@Data
@Document(indexName = "articles")  // 定义为 Elasticsearch 索引
@JsonIgnoreProperties(ignoreUnknown = true)  // 忽略未识别的字
public class Article {

    @Id  // 标记为 Elasticsearch 的文档ID
    private Long id;

    @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
    private String title;

    @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
    private String content;  // Markdown 格式内容

    @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
    private String author;  // 使用 IK 分词器进行分词和搜索

    @Field(type = FieldType.Long)
    private Long userId;  // 关联的用户ID

    @Field(type = FieldType.Date, format = {}, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
    private OffsetDateTime createdAt;

    @Field(type = FieldType.Date, format = {}, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
    private OffsetDateTime updatedAt;
}

10. ArticleRepository.java

package info.liberx.articleservice.repository;

import info.liberx.articleservice.model.Article;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ArticleRepository extends ElasticsearchRepository<Article, Long> {

}

11. ArticleService.java

package info.liberx.articleservice.service;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import info.liberx.articleservice.mapper.ArticleMapper;
import info.liberx.articleservice.model.Article;
import info.liberx.articleservice.repository.ArticleRepository;
import jakarta.annotation.PostConstruct;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Service
public class ArticleService {

    private final ArticleMapper articleMapper;
    private final ArticleRepository articleRepository;
    private final ElasticsearchClient elasticsearchClient;

    public ArticleService(ElasticsearchClient elasticsearchClient, ArticleMapper articleMapper, ArticleRepository articleRepository) {
        this.elasticsearchClient = elasticsearchClient;
        this.articleMapper = articleMapper;
        this.articleRepository = articleRepository;
    }

    @PostConstruct
    // 同步数据库中的所有文章到 Elasticsearch
    public void indexAllArticles() {
        List<Article> articles = articleMapper.findAllArticles();
        articleRepository.saveAll(articles);
    }

    public int createArticle(Article article) {
        int result = articleMapper.insertArticle(article);
        if (result > 0) {
            articleRepository.save(article);
        }
        return result;
    }

    public Article getArticleById(Long id) {
        return articleMapper.findArticleById(id);
    }


    // 分页查询文章
    public PageInfo<Article> getArticlesByUserId(Long userId, int page, int size) {
        // 使用 PageHelper 设置分页参数
        PageHelper.startPage(page, size);
        // 查询结果
        List<Article> articles = articleMapper.findArticlesByUserId(userId);
        // 包装查询结果为 PageInfo 对象
        return new PageInfo<>(articles);
    }

    public int updateArticle(Article article) {
        int result = articleMapper.updateArticle(article);
        if (result > 0) {
            articleRepository.save(article);
        }
        return result;
    }

    public int deleteArticle(Long id) {
        int result = articleMapper.deleteArticle(id);
        if (result > 0) {
            articleRepository.deleteById(id);
        }
        return result;
    }

    public Page<Article> searchArticles(String keyword, Pageable pageable) throws IOException {
        // 构建布尔查询,首先匹配完全符合的记录,然后匹配部分符合的记录
        Query query = QueryBuilders.bool()
                .should(QueryBuilders.multiMatch()
                        .fields("title", "content", "author")
                        .query(keyword)
                        .type(co.elastic.clients.elasticsearch._types.query_dsl.TextQueryType.Phrase) // 完全匹配
                        .boost(2.0f) // 提高完全匹配的权重
                        .build()._toQuery())
                .should(QueryBuilders.multiMatch()
                        .fields("title", "content", "author")
                        .query(keyword)
                        .build()._toQuery()) // 部分匹配
                .build()._toQuery();

        // 构建搜索请求,设置索引、查询条件、高亮设置、分页参数
        SearchRequest searchRequest = new SearchRequest.Builder()
                .index("articles")
                .query(query)
                .highlight(h -> h
                        .fields("title", f -> f.preTags("<strong>").postTags("</strong>"))
                        .fields("content", f -> f.preTags("<strong>").postTags("</strong>"))
                        .fields("author", f -> f.preTags("<strong>").postTags("</strong>"))
                )
                .from((int) pageable.getOffset())
                .size(pageable.getPageSize())
                .build();

        // 执行搜索请求
        SearchResponse<Article> searchResponse = elasticsearchClient.search(searchRequest, Article.class);
        HitsMetadata<Article> hitsMetadata = searchResponse.hits();

        // 处理搜索结果,提取高亮信息并设置到对应的Article对象中
        List<Article> articles = hitsMetadata.hits().stream()
                .map(hit -> {
                    Article article = hit.source();
                    if (article != null && hit.highlight() != null) {
                        hit.highlight().forEach((field, highlights) -> {
                            switch (field) {
                                case "title":
                                    article.setTitle(String.join(" ", highlights));
                                    break;
                                case "content":
                                    article.setContent(String.join(" ", highlights));
                                    break;
                                case "author":
                                    article.setAuthor(String.join(" ", highlights));
                                    break;
                            }
                        });
                    }
                    return article;
                }).collect(Collectors.toList());

        // 返回包含分页和高亮结果的Page对象
        return new PageImpl<>(articles, pageable, Objects.requireNonNull(hitsMetadata.total()).value());
    }

}

12. ArticleServiceApplication.java

package info.liberx.articleservice;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration;
import org.springframework.data.web.config.EnableSpringDataWebSupport;

@SpringBootApplication(exclude = {LoadBalancerAutoConfiguration.class, LoadBalancerBeanPostProcessorAutoConfiguration.class})
@EnableDiscoveryClient
@EnableSpringDataWebSupport(pageSerializationMode = EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO)
public class ArticleServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(ArticleServiceApplication.class, args);
    }
}

13. 测试接口(postman)

13.1 测试创建文章 (POST /articles)

POST http://localhost:8002/articles

Body:
选择 raw -> JSON 格式,示例如下:

{
    "title": "Spring Boot 保姆级教程",
    "content": "本文介绍如何使用 Spring Boot 快速创建一个应用程序。",
    "author": "张三",
    "userId": 1
}

在这里插入图片描述

13.2 测试根据文章 ID 获取文章 (GET /articles/{id})

GET http://localhost:8002/articles/1

在这里插入图片描述

13.3 测试更新文章 (PUT /articles/{id})

PUT http://localhost:8002/articles/1

Body:
选择 raw -> JSON 格式:

{
    "title": "Spring Boot 入门教程 - 更新",
    "content": "本文详细介绍了如何使用 Spring Boot 创建应用程序,并提供更新。",
    "author": "张三",
    "userId": 1
}

13.4 测试删除文章 (DELETE /articles/{id})

DELETE http://localhost:8002/articles/1

13.5 测试搜索文章 (GET /articles/search)

GET http://localhost:8002/articles/search?keyword=Spring&page=0&size=10

在这里插入图片描述

13.6 测试获取某用户的所有文章并分页 (GET /articles/get/{userid})

GET http://localhost:8002/articles/get/1?page=1&size=10

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2065256.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringCloudAlibaba Seata分布式事务

分布式事务 事务是数据库的概念&#xff0c;数据库事务&#xff08;ACID&#xff1a;原子性、一致性、隔离性和持久性&#xff09;&#xff1b; 分布式事务的产生&#xff0c;是由于数据库的拆分和分布式架构(微服务)带来的&#xff0c;在常规情况下&#xff0c;我们在一个进…

自己动手写CPU_step4_逻辑运算|移位指令

序 上一篇中我们解决了流水线的数据相关问题&#xff0c;本篇将添加多条逻辑运算和移位运算指令&#xff0c;这些指令的格式严格按照MIPS的指令格式设计。 MIPS指令格式 由于本人也是处于学习的一个过程&#xff0c;如有不对之处&#xff0c;还请大牛指正。 就逻辑运算和移位运…

【软件逆向】第11课,软件逆向安全工程师之windows API函数,每天5分钟学习逆向吧!

资料获取 关注作者&#xff0c;备注课程编号&#xff0c;获取本课配套课件和工具程序。 干货开始-windows API函数。 微软官方提供的应用程序接口&#xff0c;是一些预先定义的函数&#xff0c;目的是提供应用程序与开发人员基于某软件或硬件提供的能力。 地址&#xff1a;h…

java基础 之 了解final

文章目录 定义使用及规则修饰类修饰方法修饰变量修饰成员变量修饰局部变量final与static共同修饰变量final修饰的变量和普通变量的区别 本篇文章代码就不附上了&#xff0c;建议大家实际敲一敲&#xff0c;更能加快理解 定义 final表示”最后的&#xff0c;最终的“含义&#…

精益思维赋能机器人行业的三大维度

在日新月异的科技浪潮中&#xff0c;机器人行业正以前所未有的速度蓬勃发展&#xff0c;成为推动产业升级与转型的关键力量。然而&#xff0c;如何在激烈的市场竞争中脱颖而出&#xff0c;实现高效、灵活与可持续的发展&#xff1f;精益思维&#xff0c;这一源自制造业的管理哲…

【el-switch更改高、宽、颜色样式】深入浅出element ui的switch同页面存在多个更改样式互不影响

1.技术&#xff1a; “vue”: “^2.6.14”, “element-ui”: “^2.15.6”, 2.需求&#xff1a; 同一个页面存在多个switch组件时&#xff0c; 需要更改各自的高度、宽度、选择颜色、非选中颜色等样式&#xff0c; 并且样式隔离互不影响&#xff01; 3.效果图&#xff1a; 4.重要…

C++动态规划(背包问题)

目录 一&#xff1a;动态规划是什么 二.动态规划的运用 &#xff08;1&#xff09;.用动态规划解决重复子问题 &#xff08;2&#xff09;.动态规划使用的条件与流程 Ⅰ.动态规划的使用条件&#xff1a; Ⅱ.动态规划的使用流程 &#xff08;3&#xff09;.背包问题 三.…

IO进程(学习)2024.8.22

信号 信号函数 信号处理函数 #include <signal.h> sighandler_t signal(int signum, sighandler_t handler); 功能&#xff1a;信号处理函数 参数&#xff1a;signum&#xff1a;要处理的信号 handler&#xff1a;信号处理方式 SIG…

基于矢量控制器的PMSM永磁同步电机速度控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于矢量控制器的PMSM永磁同步电机速度控制系统simulink建模与仿真&#xff0c;仿真输出电机转速跟踪曲线&#xff0c;PID控制器输出曲线以及Te输出曲线。 2.系统仿真结果 &…

kafka的一个有趣问题(BUG)

这是我的第104篇原创文章 问题由来 在使用kafka时&#xff0c;创建topic&#xff0c;对某个topic进行扩分区的操作&#xff0c;想必大家肯定都使用过。尤其是集群进行扩容时&#xff0c;对流量较大的topic进行扩分区操作。一般而言&#xff0c;期望的效果是&#xff1a;新扩的分…

AI在医学领域:HYDEN一种针对医学图像和报告的跨模态表示学习方法

近年来&#xff0c;跨模态文本-图像表示学习在诸多领域取得了显著的突破&#xff0c;尤其是在零样本学习和图像-文本检索等任务上。这一成果的取得很大程度上归功于大量弱监督的图像-文本配对数据的利用&#xff0c;这些数据有效地增强了视觉-语言表示学习的能力。在医学成像领…

如何保证每次生成的都同一张人脸?AI绘画Stable Diffusion的Reference only插件人物一致性教程

Ai绘画有一个很现实的问题&#xff0c;要保证每次画出的都是同一个人物的话&#xff0c;很费劲。 Midjourney就不必说了&#xff0c;人物的高度一致性一直得不到很好的解决。而在Stable Diffusion&#xff08;SD&#xff09;中&#xff0c;常用办法是通过同一个Seed值&#xf…

Linux宝塔面板使用教程 - Centos/Alibaba Cloud Linux,解放命令实现可视化

使用前注意事项&#xff1a;为了您的正常使用&#xff0c;请确保使用全新或纯净的系统安装宝塔面板&#xff0c;不支持已部署项目/环境的系统安装 1.安装命令 yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh &&…

Godot《躲避小兵》实战之创建游戏主场景

游戏主场景 现在是时候将我们所做的一切整合到一个可玩的游戏场景中了。 创建新场景并添加一个 Node节点&#xff0c;命名为 Main。&#xff08;我们之所以使用 Node 而不是 Node2D&#xff0c;是因为这个节点会作为处理游戏逻辑的容器使用。本身是不需要 2D 功能的。&#x…

ZooKeeper 的3种部署模式

ZooKeeper 的3种部署模式 1. 单机模式&#xff08;Standalone Mode&#xff09;2. 伪集群模式&#xff08;Pseudo-Cluster Mode&#xff09;3. 集群模式&#xff08;Cluster Mode&#xff09; &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496;…

[000-01-011].第2节:持久层方案的对比

我的后端学习大纲 MyBatis学习大纲 1.持久层解决方案&#xff1a; 1.1.面试1&#xff1a;请说一说持久层解决方案有哪些&#xff1f;&#xff1f;&#xff1f; 1.jdbc JDBC为访问不同的数据库提供了一种统一的途径&#xff0c;为开发者屏蔽了一些细节问题。Java程序员使用JDB…

Vodafone 推出了与 Wi-Fi 竞争的基于树莓派私人5G技术

随着全球5G网络的逐步推出&#xff0c;在其过程中遇到了可预见的起起伏伏&#xff0c;并且蜂窝技术也开始进入另一个无线技术 Wi-Fi &#xff0c;并且已经占据的市场。私有5G网络&#xff08;即个人或公司建立自己的全设施蜂窝网络&#xff09;如今正在寻找曾经属于Wi-Fi的唯一…

Unity低延迟播放RTSP视频流

Unity播放RTSP视频流这个功能在好几个项目中用到&#xff0c;虽然有一些现成的插件&#xff08;VLC for unity、UMP&#xff09;可以使用&#xff0c;但是延迟高&#xff08;300毫秒以上&#xff09;的问题一直没法解决。 最近终于下定决心来解决这个问题&#xff0c;经过几天…

基于 Jenkins、Gitlab、Harbor、Helm 和 Kubernetes 的 CI/CD

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

【微服务部署】Linux部署微服务启动报ORA-01005

问题背景&#xff1a; Linux机器部署springboot微服务&#xff0c;部署完成后发现无法启动&#xff0c;后台报ORA-01005错误。 解决方案&#xff1a; 1.检查当前服务器是否已安装oracle客户端 命令行执行sqlplus username/passwd实例名&#xff0c;如果执行成功&#xff0c;说…