代码改变世界

完整教程:Spring Boot 2.5集成Elasticsearch(亲测)

2025-10-03 11:04  tlnshuju  阅读(2)  评论(0)    收藏  举报

您说得对!Spring Boot 2.5.10 确实自带了 Elasticsearch 的集成,我们可以使用 Spring Data Elasticsearch 而不需要单独引入 elasticsearch-rest-high-level-client。让我为您提供完整的 Elasticsearch 7.12 集成方案。

项目目录结构

text

src/main/java/com/example/esdemo/
├── config/
│   └── ElasticsearchConfig.java
├── entity/
│   └── UserEs.java
├── repository/
│   └── UserEsRepository.java
├── service/
│   └── UserEsService.java
├── controller/
│   └── UserEsController.java
└── ESDemoApplication.java

src/main/resources/
├── application.yml
└── logback-spring.xml

1. Maven 依赖 (pom.xml)

xml



    4.0.0
    
    
        org.springframework.boot
        spring-boot-starter-parent
        2.5.10
    
    
    com.example
    es-demo
    1.0.0
    
    
        1.8
    
    
    
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
        
            org.springframework.boot
            spring-boot-starter-data-elasticsearch
        
        
        
        
            org.projectlombok
            lombok
            true
        
        
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    
                        
                            org.projectlombok
                            lombok
                        
                    
                
            
        
    

2. 配置文件 (application.yml)

yaml

spring:
  elasticsearch:
    rest:
      # Elasticsearch 集群节点地址
      uris: http://localhost:9200,http://localhost:9201,http://localhost:9202
      # 连接超时时间
      connection-timeout: 10s
      # 读取超时时间
      read-timeout: 30s
      # 认证信息(如果Elasticsearch开启了安全认证)
      # username: elastic
      # password: your_password

# 应用配置
app:
  elasticsearch:
    index:
      user: user_es_index
    settings:
      shards: 3
      replicas: 1

# 服务器配置
server:
  port: 8080
  servlet:
    context-path: /

# 日志配置
logging:
  level:
    com.example.esdemo: DEBUG
    org.springframework.data.elasticsearch: DEBUG
    org.elasticsearch: WARN
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
  file:
    name: logs/es-demo.log

3. 配置层 (Config)

ElasticsearchConfig.java

java

package com.example.esdemo.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;

@Slf4j
@Configuration
@EnableElasticsearchRepositories(basePackages = "com.example.esdemo.repository")
public class ElasticsearchConfig {
    
    /**
     * Spring Boot 2.5.10 自动配置了 ElasticsearchRestTemplate 和 RestHighLevelClient
     * 我们不需要手动配置,只需要通过 @EnableElasticsearchRepositories 启用 Repository 支持
     */
    
    public ElasticsearchConfig() {
        log.info("Elasticsearch配置初始化完成 - 使用Spring Boot自动配置");
    }
}

4. 实体层 (Entity)

UserEs.java

java

package com.example.esdemo.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
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 org.springframework.data.elasticsearch.annotations.Setting;

import java.time.LocalDateTime;
import java.util.Map;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "user_es_index")
@Setting(shards = 3, replicas = 1)  // 索引设置
public class UserEs {
    
    @Id
    private String id;
    
    @Field(type = FieldType.Long)
    private Long userId;
    
    @Field(type = FieldType.Keyword)
    private String username;
    
    @Field(type = FieldType.Text, analyzer = "standard", searchAnalyzer = "standard")
    private String realName;
    
    @Field(type = FieldType.Integer)
    private Integer age;
    
    @Field(type = FieldType.Keyword)
    private String email;
    
    @Field(type = FieldType.Text, analyzer = "standard")
    private String address;
    
    @Field(type = FieldType.Keyword)
    private String phone;
    
    @Field(type = FieldType.Date)
    private LocalDateTime createTime;
    
    @Field(type = FieldType.Date)
    private LocalDateTime updateTime;
    
    @Field(type = FieldType.Boolean)
    private Boolean status;
    
    @Field(type = FieldType.Object, enabled = false)
    private Map extraInfo;
    
    // 便捷构造方法
    public UserEs(Long userId, String username, String realName, Integer age, 
                  String email, String address, String phone) {
        this.userId = userId;
        this.username = username;
        this.realName = realName;
        this.age = age;
        this.email = email;
        this.address = address;
        this.phone = phone;
        this.createTime = LocalDateTime.now();
        this.updateTime = LocalDateTime.now();
        this.status = true;
    }
    
    // 静态工厂方法
    public static UserEs createUser(Long userId, String username, String realName, 
                                   Integer age, String email, String address, String phone) {
        return new UserEs(userId, username, realName, age, email, address, phone);
    }
}

5. 数据访问层 (Repository)

UserEsRepository.java

java

package com.example.esdemo.repository;

import com.example.esdemo.entity.UserEs;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface UserEsRepository extends ElasticsearchRepository {
    
    // ==================== 基本查询方法 ====================
    
    /**
     * 根据用户名查询(精确匹配)
     */
    Optional findByUsername(String username);
    
    /**
     * 根据邮箱查询(精确匹配)
     */
    Optional findByEmail(String email);
    
    /**
     * 根据手机号查询(精确匹配)
     */
    Optional findByPhone(String phone);
    
    /**
     * 根据用户ID查询
     */
    Optional findByUserId(Long userId);
    
    // ==================== 范围查询方法 ====================
    
    /**
     * 根据年龄范围查询
     */
    List findByAgeBetween(Integer minAge, Integer maxAge);
    
    /**
     * 查询年龄大于指定值的用户
     */
    List findByAgeGreaterThan(Integer age);
    
    /**
     * 查询年龄小于指定值的用户
     */
    List findByAgeLessThan(Integer age);
    
    // ==================== 模糊查询方法 ====================
    
    /**
     * 根据真实姓名模糊查询
     */
    List findByRealNameContaining(String realName);
    
    /**
     * 根据地址模糊查询
     */
    List findByAddressContaining(String address);
    
    /**
     * 根据地址模糊查询并分页
     */
    Page findByAddressContaining(String address, Pageable pageable);
    
    // ==================== 状态查询方法 ====================
    
    /**
     * 根据状态查询用户
     */
    List findByStatus(Boolean status);
    
    /**
     * 根据状态查询用户并分页
     */
    Page findByStatus(Boolean status, Pageable pageable);
    
    // ==================== 组合查询方法 ====================
    
    /**
     * 根据用户名和年龄查询
     */
    List findByUsernameAndAge(String username, Integer age);
    
    /**
     * 根据状态和年龄范围查询
     */
    List findByStatusAndAgeBetween(Boolean status, Integer minAge, Integer maxAge);
    
    /**
     * 根据真实姓名和状态查询
     */
    List findByRealNameContainingAndStatus(String realName, Boolean status);
    
    // ==================== 排序查询方法 ====================
    
    /**
     * 根据创建时间倒序查询所有用户
     */
    List findAllByOrderByCreateTimeDesc();
    
    /**
     * 根据年龄升序查询所有用户
     */
    List findAllByOrderByAgeAsc();
}

6. 服务层 (Service)

UserEsService.java

java

package com.example.esdemo.service;

import com.example.esdemo.entity.UserEs;
import com.example.esdemo.repository.UserEsRepository;
import lombok.extern.slf4j.Slf4j;
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.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

@Slf4j
@Service
public class UserEsService {

    @Autowired
    private UserEsRepository userEsRepository;

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    // ==================== 索引管理操作 ====================

    /**
     * 创建索引
     */
    public boolean createIndex() {
        try {
            IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(UserEs.class);
            if (indexOperations.exists()) {
                log.warn("索引已存在: user_es_index");
                return true;
            }
            
            boolean created = indexOperations.create();
            if (created) {
                // 创建映射
                Document mapping = indexOperations.createMapping(UserEs.class);
                indexOperations.putMapping(mapping);
                log.info("索引创建成功: user_es_index");
            }
            return created;
        } catch (Exception e) {
            log.error("创建索引失败", e);
            return false;
        }
    }

    /**
     * 删除索引
     */
    public boolean deleteIndex() {
        try {
            IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(UserEs.class);
            if (!indexOperations.exists()) {
                log.warn("索引不存在: user_es_index");
                return true;
            }
            
            boolean deleted = indexOperations.delete();
            log.info("索引删除结果: {}", deleted);
            return deleted;
        } catch (Exception e) {
            log.error("删除索引失败", e);
            return false;
        }
    }

    /**
     * 检查索引是否存在
     */
    public boolean indexExists() {
        try {
            IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(UserEs.class);
            return indexOperations.exists();
        } catch (Exception e) {
            log.error("检查索引存在失败", e);
            return false;
        }
    }

    /**
     * 刷新索引
     */
    public void refreshIndex() {
        try {
            IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(UserEs.class);
            indexOperations.refresh();
            log.info("索引刷新完成");
        } catch (Exception e) {
            log.error("刷新索引失败", e);
        }
    }

    /**
     * 获取索引信息
     */
    public Map getIndexInfo() {
        Map info = new HashMap<>();
        try {
            IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(UserEs.class);
            info.put("exists", indexOperations.exists());
            info.put("settings", indexOperations.getSettings());
            info.put("mapping", indexOperations.getMapping());
            info.put("indexName", "user_es_index");
        } catch (Exception e) {
            log.error("获取索引信息失败", e);
            info.put("error", e.getMessage());
        }
        return info;
    }

    // ==================== 文档CRUD操作 ====================

    /**
     * 添加单个用户
     */
    public UserEs saveUser(UserEs userEs) {
        try {
            if (userEs.getCreateTime() == null) {
                userEs.setCreateTime(LocalDateTime.now());
            }
            userEs.setUpdateTime(LocalDateTime.now());
            if (userEs.getStatus() == null) {
                userEs.setStatus(true);
            }
            
            UserEs savedUser = userEsRepository.save(userEs);
            log.info("用户保存成功, ID: {}, 用户名: {}", savedUser.getId(), savedUser.getUsername());
            return savedUser;
        } catch (Exception e) {
            log.error("保存用户失败: {}", userEs, e);
            throw new RuntimeException("保存用户失败: " + e.getMessage(), e);
        }
    }

    /**
     * 批量添加用户
     */
    public List saveAllUsers(List userEsList) {
        try {
            userEsList.forEach(user -> {
                if (user.getCreateTime() == null) {
                    user.setCreateTime(LocalDateTime.now());
                }
                user.setUpdateTime(LocalDateTime.now());
                if (user.getStatus() == null) {
                    user.setStatus(true);
                }
            });
            
            Iterable savedUsers = userEsRepository.saveAll(userEsList);
            List result = StreamSupport.stream(savedUsers.spliterator(), false)
                    .collect(Collectors.toList());
            
            log.info("批量保存用户成功, 数量: {}", result.size());
            return result;
        } catch (Exception e) {
            log.error("批量保存用户失败", e);
            throw new RuntimeException("批量保存用户失败: " + e.getMessage(), e);
        }
    }

    /**
     * 根据ID查询用户
     */
    public Optional findById(String id) {
        try {
            return userEsRepository.findById(id);
        } catch (Exception e) {
            log.error("根据ID查询用户失败, ID: {}", id, e);
            return Optional.empty();
        }
    }

    /**
     * 根据用户ID查询
     */
    public Optional findByUserId(Long userId) {
        try {
            return userEsRepository.findByUserId(userId);
        } catch (Exception e) {
            log.error("根据用户ID查询失败, UserID: {}", userId, e);
            return Optional.empty();
        }
    }

    /**
     * 根据用户名查询
     */
    public Optional findByUsername(String username) {
        try {
            return userEsRepository.findByUsername(username);
        } catch (Exception e) {
            log.error("根据用户名查询失败, 用户名: {}", username, e);
            return Optional.empty();
        }
    }

    /**
     * 查询所有用户
     */
    public List findAll() {
        try {
            Iterable users = userEsRepository.findAll();
            return StreamSupport.stream(users.spliterator(), false)
                    .collect(Collectors.toList());
        } catch (Exception e) {
            log.error("查询所有用户失败", e);
            return Collections.emptyList();
        }
    }

    /**
     * 分页查询所有用户
     */
    public Page findAllWithPage(int page, int size) {
        try {
            Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createTime"));
            return userEsRepository.findAll(pageable);
        } catch (Exception e) {
            log.error("分页查询用户失败, page: {}, size: {}", page, size, e);
            throw new RuntimeException("分页查询失败: " + e.getMessage(), e);
        }
    }

    /**
     * 更新用户
     */
    public UserEs updateUser(UserEs userEs) {
        try {
            if (userEs.getId() == null) {
                throw new IllegalArgumentException("用户ID不能为空");
            }
            
            if (!userEsRepository.existsById(userEs.getId())) {
                throw new RuntimeException("用户不存在, ID: " + userEs.getId());
            }
            
            userEs.setUpdateTime(LocalDateTime.now());
            UserEs updatedUser = userEsRepository.save(userEs);
            log.info("用户更新成功, ID: {}", updatedUser.getId());
            return updatedUser;
        } catch (Exception e) {
            log.error("更新用户失败: {}", userEs, e);
            throw new RuntimeException("更新用户失败: " + e.getMessage(), e);
        }
    }

    /**
     * 部分更新用户
     */
    public UserEs partialUpdate(String id, Map updates) {
        try {
            Optional userOpt = userEsRepository.findById(id);
            if (!userOpt.isPresent()) {
                throw new RuntimeException("用户不存在, ID: " + id);
            }
            
            UserEs user = userOpt.get();
            updates.forEach((key, value) -> {
                switch (key) {
                    case "realName":
                        user.setRealName((String) value);
                        break;
                    case "age":
                        user.setAge((Integer) value);
                        break;
                    case "email":
                        user.setEmail((String) value);
                        break;
                    case "address":
                        user.setAddress((String) value);
                        break;
                    case "phone":
                        user.setPhone((String) value);
                        break;
                    case "status":
                        user.setStatus((Boolean) value);
                        break;
                    case "extraInfo":
                        user.setExtraInfo((Map) value);
                        break;
                }
            });
            user.setUpdateTime(LocalDateTime.now());
            
            UserEs updatedUser = userEsRepository.save(user);
            log.info("用户部分更新成功, ID: {}", id);
            return updatedUser;
        } catch (Exception e) {
            log.error("部分更新用户失败, ID: {}, updates: {}", id, updates, e);
            throw new RuntimeException("部分更新失败: " + e.getMessage(), e);
        }
    }

    /**
     * 根据ID删除用户
     */
    public boolean deleteById(String id) {
        try {
            if (!userEsRepository.existsById(id)) {
                log.warn("用户不存在, 无法删除, ID: {}", id);
                return false;
            }
            
            userEsRepository.deleteById(id);
            log.info("用户删除成功, ID: {}", id);
            return true;
        } catch (Exception e) {
            log.error("删除用户失败, ID: {}", id, e);
            return false;
        }
    }

    /**
     * 根据用户名删除用户
     */
    public boolean deleteByUsername(String username) {
        try {
            Optional userOpt = userEsRepository.findByUsername(username);
            if (!userOpt.isPresent()) {
                log.warn("用户不存在, 无法删除, 用户名: {}", username);
                return false;
            }
            
            userEsRepository.delete(userOpt.get());
            log.info("用户删除成功, 用户名: {}", username);
            return true;
        } catch (Exception e) {
            log.error("根据用户名删除用户失败, 用户名: {}", username, e);
            return false;
        }
    }

    /**
     * 批量删除用户
     */
    public void deleteAll(List ids) {
        try {
            List usersToDelete = ids.stream()
                    .map(id -> {
                        UserEs user = new UserEs();
                        user.setId(id);
                        return user;
                    })
                    .collect(Collectors.toList());
            
            userEsRepository.deleteAll(usersToDelete);
            log.info("批量删除用户成功, 数量: {}", ids.size());
        } catch (Exception e) {
            log.error("批量删除用户失败", e);
            throw new RuntimeException("批量删除失败: " + e.getMessage(), e);
        }
    }

    // ==================== 高级查询操作 ====================

    /**
     * 根据年龄范围查询
     */
    public List findByAgeRange(Integer minAge, Integer maxAge) {
        try {
            return userEsRepository.findByAgeBetween(minAge, maxAge);
        } catch (Exception e) {
            log.error("根据年龄范围查询失败, minAge: {}, maxAge: {}", minAge, maxAge, e);
            return Collections.emptyList();
        }
    }

    /**
     * 根据状态查询用户
     */
    public List findByStatus(Boolean status) {
        try {
            return userEsRepository.findByStatus(status);
        } catch (Exception e) {
            log.error("根据状态查询用户失败, status: {}", status, e);
            return Collections.emptyList();
        }
    }

    /**
     * 根据地址模糊查询
     */
    public List findByAddress(String address) {
        try {
            return userEsRepository.findByAddressContaining(address);
        } catch (Exception e) {
            log.error("根据地址查询用户失败, address: {}", address, e);
            return Collections.emptyList();
        }
    }

    /**
     * 复杂搜索:多条件组合查询
     */
    public List complexSearch(String keyword, Integer minAge, Integer maxAge, 
                                     String address, Boolean status) {
        try {
            NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
            
            // 构建布尔查询
            org.elasticsearch.index.query.BoolQueryBuilder boolQuery = 
                org.elasticsearch.index.query.QueryBuilders.boolQuery();
            
            // 关键词搜索
            if (StringUtils.hasText(keyword)) {
                boolQuery.should(org.elasticsearch.index.query.QueryBuilders
                    .matchQuery("username", keyword));
                boolQuery.should(org.elasticsearch.index.query.QueryBuilders
                    .matchQuery("realName", keyword));
                boolQuery.should(org.elasticsearch.index.query.QueryBuilders
                    .matchQuery("address", keyword));
                boolQuery.minimumShouldMatch(1);
            }
            
            // 年龄范围过滤
            if (minAge != null || maxAge != null) {
                org.elasticsearch.index.query.RangeQueryBuilder rangeQuery = 
                    org.elasticsearch.index.query.QueryBuilders.rangeQuery("age");
                if (minAge != null) {
                    rangeQuery.gte(minAge);
                }
                if (maxAge != null) {
                    rangeQuery.lte(maxAge);
                }
                boolQuery.filter(rangeQuery);
            }
            
            // 地址过滤
            if (StringUtils.hasText(address)) {
                boolQuery.filter(org.elasticsearch.index.query.QueryBuilders
                    .wildcardQuery("address", "*" + address + "*"));
            }
            
            // 状态过滤
            if (status != null) {
                boolQuery.filter(org.elasticsearch.index.query.QueryBuilders
                    .termQuery("status", status));
            }
            
            NativeSearchQuery query = queryBuilder
                    .withQuery(boolQuery)
                    .withSort(Sort.by(Sort.Direction.DESC, "createTime"))
                    .build();
            
            return elasticsearchRestTemplate.queryForList(query, UserEs.class);
        } catch (Exception e) {
            log.error("复杂搜索失败, keyword: {}, minAge: {}, maxAge: {}, address: {}, status: {}", 
                     keyword, minAge, maxAge, address, status, e);
            return Collections.emptyList();
        }
    }

    // ==================== 统计操作 ====================

    /**
     * 统计用户总数
     */
    public long count() {
        try {
            return userEsRepository.count();
        } catch (Exception e) {
            log.error("统计用户总数失败", e);
            return 0;
        }
    }

    /**
     * 根据状态统计用户数量
     */
    public long countByStatus(Boolean status) {
        try {
            return userEsRepository.findByStatus(status).size();
        } catch (Exception e) {
            log.error("根据状态统计用户数量失败, status: {}", status, e);
            return 0;
        }
    }

    /**
     * 检查用户名是否存在
     */
    public boolean existsByUsername(String username) {
        try {
            return userEsRepository.findByUsername(username).isPresent();
        } catch (Exception e) {
            log.error("检查用户名是否存在失败, username: {}", username, e);
            return false;
        }
    }

    /**
     * 检查邮箱是否存在
     */
    public boolean existsByEmail(String email) {
        try {
            return userEsRepository.findByEmail(email).isPresent();
        } catch (Exception e) {
            log.error("检查邮箱是否存在失败, email: {}", email, e);
            return false;
        }
    }
}

7. 控制层 (Controller)

UserEsController.java

java

package com.example.esdemo.controller;

import com.example.esdemo.entity.UserEs;
import com.example.esdemo.service.UserEsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@Slf4j
@RestController
@RequestMapping("/api/es/users")
public class UserEsController {

    @Autowired
    private UserEsService userEsService;

    // ==================== 索引管理接口 ====================

    /**
     * 创建索引
     */
    @PostMapping("/index")
    public ResponseEntity> createIndex() {
        log.info("请求创建索引");
        try {
            boolean success = userEsService.createIndex();
            Map result = createSuccessResult(success, 
                success ? "索引创建成功" : "索引创建失败");
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("创建索引异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("创建索引异常: " + e.getMessage()));
        }
    }

    /**
     * 删除索引
     */
    @DeleteMapping("/index")
    public ResponseEntity> deleteIndex() {
        log.info("请求删除索引");
        try {
            boolean success = userEsService.deleteIndex();
            Map result = createSuccessResult(success,
                success ? "索引删除成功" : "索引删除失败");
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("删除索引异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("删除索引异常: " + e.getMessage()));
        }
    }

    /**
     * 检查索引是否存在
     */
    @GetMapping("/index/exists")
    public ResponseEntity> indexExists() {
        log.info("请求检查索引是否存在");
        try {
            boolean exists = userEsService.indexExists();
            Map result = createSuccessResult(true, "查询成功");
            result.put("exists", exists);
            result.put("indexName", "user_es_index");
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("检查索引存在异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("检查索引存在异常: " + e.getMessage()));
        }
    }

    /**
     * 获取索引信息
     */
    @GetMapping("/index/info")
    public ResponseEntity> getIndexInfo() {
        log.info("请求获取索引信息");
        try {
            Map indexInfo = userEsService.getIndexInfo();
            Map result = createSuccessResult(true, "获取索引信息成功");
            result.put("indexInfo", indexInfo);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("获取索引信息异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("获取索引信息异常: " + e.getMessage()));
        }
    }

    /**
     * 刷新索引
     */
    @PostMapping("/index/refresh")
    public ResponseEntity> refreshIndex() {
        log.info("请求刷新索引");
        try {
            userEsService.refreshIndex();
            Map result = createSuccessResult(true, "索引刷新成功");
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("刷新索引异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("刷新索引异常: " + e.getMessage()));
        }
    }

    // ==================== 文档CRUD接口 ====================

    /**
     * 添加用户
     */
    @PostMapping
    public ResponseEntity> addUser(@RequestBody UserEs userEs) {
        log.info("请求添加用户: {}", userEs.getUsername());
        try {
            UserEs savedUser = userEsService.saveUser(userEs);
            Map result = createSuccessResult(true, "用户添加成功");
            result.put("data", savedUser);
            return ResponseEntity.status(HttpStatus.CREATED).body(result);
        } catch (Exception e) {
            log.error("添加用户异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("添加用户失败: " + e.getMessage()));
        }
    }

    /**
     * 批量添加用户
     */
    @PostMapping("/batch")
    public ResponseEntity> batchAddUsers(@RequestBody List userEsList) {
        log.info("请求批量添加用户, 数量: {}", userEsList.size());
        try {
            List savedUsers = userEsService.saveAllUsers(userEsList);
            Map result = createSuccessResult(true, "批量添加用户成功");
            result.put("data", savedUsers);
            result.put("count", savedUsers.size());
            return ResponseEntity.status(HttpStatus.CREATED).body(result);
        } catch (Exception e) {
            log.error("批量添加用户异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("批量添加用户失败: " + e.getMessage()));
        }
    }

    /**
     * 根据ID查询用户
     */
    @GetMapping("/{id}")
    public ResponseEntity> getUserById(@PathVariable String id) {
        log.info("请求根据ID查询用户: {}", id);
        try {
            Optional userOpt = userEsService.findById(id);
            if (userOpt.isPresent()) {
                Map result = createSuccessResult(true, "查询成功");
                result.put("data", userOpt.get());
                return ResponseEntity.ok(result);
            } else {
                return ResponseEntity.status(HttpStatus.NOT_FOUND)
                        .body(createErrorResult("用户不存在"));
            }
        } catch (Exception e) {
            log.error("根据ID查询用户异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("查询用户失败: " + e.getMessage()));
        }
    }

    /**
     * 根据用户ID查询
     */
    @GetMapping("/userId/{userId}")
    public ResponseEntity> getUserByUserId(@PathVariable Long userId) {
        log.info("请求根据用户ID查询: {}", userId);
        try {
            Optional userOpt = userEsService.findByUserId(userId);
            if (userOpt.isPresent()) {
                Map result = createSuccessResult(true, "查询成功");
                result.put("data", userOpt.get());
                return ResponseEntity.ok(result);
            } else {
                return ResponseEntity.status(HttpStatus.NOT_FOUND)
                        .body(createErrorResult("用户不存在"));
            }
        } catch (Exception e) {
            log.error("根据用户ID查询异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("查询用户失败: " + e.getMessage()));
        }
    }

    /**
     * 根据用户名查询
     */
    @GetMapping("/username/{username}")
    public ResponseEntity> getUserByUsername(@PathVariable String username) {
        log.info("请求根据用户名查询: {}", username);
        try {
            Optional userOpt = userEsService.findByUsername(username);
            if (userOpt.isPresent()) {
                Map result = createSuccessResult(true, "查询成功");
                result.put("data", userOpt.get());
                return ResponseEntity.ok(result);
            } else {
                return ResponseEntity.status(HttpStatus.NOT_FOUND)
                        .body(createErrorResult("用户不存在"));
            }
        } catch (Exception e) {
            log.error("根据用户名查询异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("查询用户失败: " + e.getMessage()));
        }
    }

    /**
     * 查询所有用户
     */
    @GetMapping
    public ResponseEntity> getAllUsers() {
        log.info("请求查询所有用户");
        try {
            List users = userEsService.findAll();
            Map result = createSuccessResult(true, "查询成功");
            result.put("data", users);
            result.put("total", users.size());
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("查询所有用户异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("查询用户失败: " + e.getMessage()));
        }
    }

    /**
     * 分页查询所有用户
     */
    @GetMapping("/page")
    public ResponseEntity> getAllUsersWithPage(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size) {
        log.info("请求分页查询用户, page: {}, size: {}", page, size);
        try {
            Page userPage = userEsService.findAllWithPage(page, size);
            Map result = createSuccessResult(true, "查询成功");
            result.put("data", userPage.getContent());
            result.put("total", userPage.getTotalElements());
            result.put("page", page);
            result.put("size", size);
            result.put("totalPages", userPage.getTotalPages());
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("分页查询用户异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("分页查询失败: " + e.getMessage()));
        }
    }

    /**
     * 更新用户
     */
    @PutMapping
    public ResponseEntity> updateUser(@RequestBody UserEs userEs) {
        log.info("请求更新用户: {}", userEs.getId());
        try {
            UserEs updatedUser = userEsService.updateUser(userEs);
            Map result = createSuccessResult(true, "用户更新成功");
            result.put("data", updatedUser);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("更新用户异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("更新用户失败: " + e.getMessage()));
        }
    }

    /**
     * 部分更新用户
     */
    @PatchMapping("/{id}")
    public ResponseEntity> partialUpdateUser(
            @PathVariable String id, 
            @RequestBody Map updates) {
        log.info("请求部分更新用户: {}, updates: {}", id, updates);
        try {
            UserEs updatedUser = userEsService.partialUpdate(id, updates);
            Map result = createSuccessResult(true, "用户部分更新成功");
            result.put("data", updatedUser);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("部分更新用户异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("部分更新用户失败: " + e.getMessage()));
        }
    }

    /**
     * 根据ID删除用户
     */
    @DeleteMapping("/{id}")
    public ResponseEntity> deleteUser(@PathVariable String id) {
        log.info("请求删除用户: {}", id);
        try {
            boolean success = userEsService.deleteById(id);
            if (success) {
                Map result = createSuccessResult(true, "用户删除成功");
                return ResponseEntity.ok(result);
            } else {
                return ResponseEntity.status(HttpStatus.NOT_FOUND)
                        .body(createErrorResult("用户不存在"));
            }
        } catch (Exception e) {
            log.error("删除用户异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("删除用户失败: " + e.getMessage()));
        }
    }

    /**
     * 根据用户名删除用户
     */
    @DeleteMapping("/username/{username}")
    public ResponseEntity> deleteUserByUsername(@PathVariable String username) {
        log.info("请求根据用户名删除用户: {}", username);
        try {
            boolean success = userEsService.deleteByUsername(username);
            if (success) {
                Map result = createSuccessResult(true, "用户删除成功");
                return ResponseEntity.ok(result);
            } else {
                return ResponseEntity.status(HttpStatus.NOT_FOUND)
                        .body(createErrorResult("用户不存在"));
            }
        } catch (Exception e) {
            log.error("根据用户名删除用户异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("删除用户失败: " + e.getMessage()));
        }
    }

    // ==================== 高级查询接口 ====================

    /**
     * 根据年龄范围查询
     */
    @GetMapping("/age/range")
    public ResponseEntity> getUsersByAgeRange(
            @RequestParam Integer minAge,
            @RequestParam Integer maxAge) {
        log.info("请求根据年龄范围查询, minAge: {}, maxAge: {}", minAge, maxAge);
        try {
            List users = userEsService.findByAgeRange(minAge, maxAge);
            Map result = createSuccessResult(true, "查询成功");
            result.put("data", users);
            result.put("total", users.size());
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("根据年龄范围查询异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("查询失败: " + e.getMessage()));
        }
    }

    /**
     * 根据状态查询用户
     */
    @GetMapping("/status/{status}")
    public ResponseEntity> getUsersByStatus(@PathVariable Boolean status) {
        log.info("请求根据状态查询用户: {}", status);
        try {
            List users = userEsService.findByStatus(status);
            Map result = createSuccessResult(true, "查询成功");
            result.put("data", users);
            result.put("total", users.size());
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("根据状态查询用户异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("查询失败: " + e.getMessage()));
        }
    }

    /**
     * 根据地址查询用户
     */
    @GetMapping("/address/{address}")
    public ResponseEntity> getUsersByAddress(@PathVariable String address) {
        log.info("请求根据地址查询用户: {}", address);
        try {
            List users = userEsService.findByAddress(address);
            Map result = createSuccessResult(true, "查询成功");
            result.put("data", users);
            result.put("total", users.size());
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("根据地址查询用户异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("查询失败: " + e.getMessage()));
        }
    }

    /**
     * 复杂搜索
     */
    @GetMapping("/search/complex")
    public ResponseEntity> complexSearch(
            @RequestParam(required = false) String keyword,
            @RequestParam(required = false) Integer minAge,
            @RequestParam(required = false) Integer maxAge,
            @RequestParam(required = false) String address,
            @RequestParam(required = false) Boolean status) {
        
        log.info("请求复杂搜索, keyword: {}, minAge: {}, maxAge: {}, address: {}, status: {}", 
                keyword, minAge, maxAge, address, status);
        
        try {
            List users = userEsService.complexSearch(keyword, minAge, maxAge, address, status);
            Map result = createSuccessResult(true, "搜索成功");
            result.put("data", users);
            result.put("total", users.size());
            result.put("searchParams", Map.of(
                "keyword", keyword,
                "minAge", minAge,
                "maxAge", maxAge,
                "address", address,
                "status", status
            ));
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("复杂搜索异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("搜索失败: " + e.getMessage()));
        }
    }

    // ==================== 统计接口 ====================

    /**
     * 统计用户总数
     */
    @GetMapping("/count")
    public ResponseEntity> getUserCount() {
        log.info("请求统计用户总数");
        try {
            long count = userEsService.count();
            Map result = createSuccessResult(true, "统计成功");
            result.put("count", count);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("统计用户总数异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("统计失败: " + e.getMessage()));
        }
    }

    /**
     * 根据状态统计用户数量
     */
    @GetMapping("/count/status/{status}")
    public ResponseEntity> countByStatus(@PathVariable Boolean status) {
        log.info("请求根据状态统计用户数量: {}", status);
        try {
            long count = userEsService.countByStatus(status);
            Map result = createSuccessResult(true, "统计成功");
            result.put("count", count);
            result.put("status", status);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("根据状态统计用户数量异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("统计失败: " + e.getMessage()));
        }
    }

    /**
     * 检查用户名是否存在
     */
    @GetMapping("/exists/username/{username}")
    public ResponseEntity> checkUsernameExists(@PathVariable String username) {
        log.info("请求检查用户名是否存在: {}", username);
        try {
            boolean exists = userEsService.existsByUsername(username);
            Map result = createSuccessResult(true, "检查成功");
            result.put("exists", exists);
            result.put("username", username);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("检查用户名是否存在异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("检查失败: " + e.getMessage()));
        }
    }

    /**
     * 检查邮箱是否存在
     */
    @GetMapping("/exists/email/{email}")
    public ResponseEntity> checkEmailExists(@PathVariable String email) {
        log.info("请求检查邮箱是否存在: {}", email);
        try {
            boolean exists = userEsService.existsByEmail(email);
            Map result = createSuccessResult(true, "检查成功");
            result.put("exists", exists);
            result.put("email", email);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("检查邮箱是否存在异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(createErrorResult("检查失败: " + e.getMessage()));
        }
    }

    // ==================== 健康检查接口 ====================

    /**
     * 健康检查
     */
    @GetMapping("/health")
    public ResponseEntity> healthCheck() {
        log.info("请求健康检查");
        try {
            boolean indexExists = userEsService.indexExists();
            long userCount = userEsService.count();
            
            Map healthInfo = new HashMap<>();
            healthInfo.put("status", "UP");
            healthInfo.put("indexExists", indexExists);
            healthInfo.put("userCount", userCount);
            healthInfo.put("timestamp", System.currentTimeMillis());
            
            Map result = createSuccessResult(true, "服务正常");
            result.put("health", healthInfo);
            
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("健康检查异常", e);
            Map healthInfo = new HashMap<>();
            healthInfo.put("status", "DOWN");
            healthInfo.put("error", e.getMessage());
            healthInfo.put("timestamp", System.currentTimeMillis());
            
            Map result = createErrorResult("服务异常");
            result.put("health", healthInfo);
            
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }

    // ==================== 工具方法 ====================

    private Map createSuccessResult(boolean success, String message) {
        Map result = new HashMap<>();
        result.put("success", success);
        result.put("message", message);
        result.put("timestamp", System.currentTimeMillis());
        return result;
    }

    private Map createErrorResult(String message) {
        Map result = new HashMap<>();
        result.put("success", false);
        result.put("message", message);
        result.put("timestamp", System.currentTimeMillis());
        return result;
    }
}

8. 应用启动类

ESDemoApplication.java

java

package com.example.esdemo;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;

import java.net.InetAddress;
import java.net.UnknownHostException;

@Slf4j
@SpringBootApplication
public class ESDemoApplication {

    public static void main(String[] args) throws UnknownHostException {
        ConfigurableApplicationContext application = SpringApplication.run(ESDemoApplication.class, args);
        
        Environment env = application.getEnvironment();
        String ip = InetAddress.getLocalHost().getHostAddress();
        String port = env.getProperty("server.port");
        String path = env.getProperty("server.servlet.context-path", "");
        
        log.info("\n----------------------------------------------------------\n\t" +
                "Spring Boot Elasticsearch Demo 应用启动成功! \n\t" +
                "Local: \t\thttp://localhost:" + port + path + "\n\t" +
                "External: \thttp://" + ip + ":" + port + path + "\n\t" +
                "Elasticsearch: " + env.getProperty("spring.elasticsearch.rest.uris") + "\n\t" +
                "API文档: \thttp://" + ip + ":" + port + path + "/api/es/users/health\n" +
                "----------------------------------------------------------");
    }
}

9. 测试数据初始化

DataInitializer.java

java

package com.example.esdemo.component;

import com.example.esdemo.entity.UserEs;
import com.example.esdemo.service.UserEsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

@Slf4j
@Component
public class DataInitializer implements CommandLineRunner {

    @Autowired
    private UserEsService userEsService;

    @Override
    public void run(String... args) throws Exception {
        log.info("开始初始化Elasticsearch测试数据...");
        
        // 检查并创建索引
        if (!userEsService.indexExists()) {
            log.info("索引不存在,开始创建索引...");
            boolean indexCreated = userEsService.createIndex();
            log.info("索引创建结果: {}", indexCreated);
            
            // 等待索引创建完成
            Thread.sleep(2000);
        } else {
            log.info("索引已存在,跳过创建");
        }
        
        // 检查是否已有数据
        long existingCount = userEsService.count();
        if (existingCount > 0) {
            log.info("已有 {} 条数据,跳过测试数据初始化", existingCount);
            return;
        }
        
        // 添加测试数据
        List testUsers = Arrays.asList(
            UserEs.createUser(1L, "zhangsan", "张三", 25, "zhangsan@example.com", "北京市朝阳区", "13800138001"),
            UserEs.createUser(2L, "lisi", "李四", 30, "lisi@example.com", "上海市浦东新区", "13800138002"),
            UserEs.createUser(3L, "wangwu", "王五", 28, "wangwu@example.com", "广州市天河区", "13800138003"),
            UserEs.createUser(4L, "zhaoliu", "赵六", 35, "zhaoliu@example.com", "深圳市南山区", "13800138004"),
            UserEs.createUser(5L, "sunqi", "孙七", 22, "sunqi@example.com", "杭州市西湖区", "13800138005"),
            UserEs.createUser(6L, "zhouba", "周八", 40, "zhouba@example.com", "成都市武侯区", "13800138006"),
            UserEs.createUser(7L, "wujiu", "吴九", 26, "wujiu@example.com", "武汉市江汉区", "13800138007"),
            UserEs.createUser(8L, "zhengshi", "郑十", 33, "zhengshi@example.com", "南京市鼓楼区", "13800138008")
        );
        
        List savedUsers = userEsService.saveAllUsers(testUsers);
        log.info("测试数据初始化完成,共添加 {} 个用户", savedUsers.size());
        
        // 验证数据
        long userCount = userEsService.count();
        log.info("当前用户总数: {}", userCount);
        
        // 刷新索引确保数据可搜索
        userEsService.refreshIndex();
        log.info("索引刷新完成,数据初始化流程结束");
    }
}

10. 全局异常处理

GlobalExceptionHandler.java

java

package com.example.esdemo.handler;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理所有未知异常
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity> handleException(Exception e) {
        log.error("系统异常: ", e);
        
        Map result = new HashMap<>();
        result.put("success", false);
        result.put("message", "系统内部错误: " + e.getMessage());
        result.put("timestamp", System.currentTimeMillis());
        
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
    }

    /**
     * 处理运行时异常
     */
    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity> handleRuntimeException(RuntimeException e) {
        log.error("运行时异常: ", e);
        
        Map result = new HashMap<>();
        result.put("success", false);
        result.put("message", "业务处理错误: " + e.getMessage());
        result.put("timestamp", System.currentTimeMillis());
        
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(result);
    }

    /**
     * 处理非法参数异常
     */
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity> handleIllegalArgumentException(IllegalArgumentException e) {
        log.error("参数异常: ", e);
        
        Map result = new HashMap<>();
        result.put("success", false);
        result.put("message", "参数错误: " + e.getMessage());
        result.put("timestamp", System.currentTimeMillis());
        
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(result);
    }
}

11. 日志配置

logback-spring.xml

xml



    
    
    
    
    
    
        
            %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
            UTF-8
        
    
    
    
        ${LOG_PATH}/${LOG_FILE}.log
        
            ${LOG_PATH}/${LOG_FILE}-%d{yyyy-MM-dd}.%i.log
            
                100MB
            
            30
            3GB
        
        
            %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
            UTF-8
        
    
    
    
        
        
    
    
    
    
    

完整项目结构回顾

text

src/main/java/com/example/esdemo/
├── config/
│   └── ElasticsearchConfig.java
├── entity/
│   └── UserEs.java
├── repository/
│   └── UserEsRepository.java
├── service/
│   └── UserEsService.java
├── controller/
│   └── UserEsController.java
├── handler/
│   └── GlobalExceptionHandler.java
├── component/
│   └── DataInitializer.java
└── ESDemoApplication.java

src/main/resources/
├── application.yml
└── logback-spring.xml

这个完整的实例包含了:

  1. Spring Boot 2.5.10 自带的 Elasticsearch 集成(无需额外依赖)

  2. 完整的 CRUD 操作(增删改查)

  3. 索引管理(创建、删除、检查索引)

  4. 复杂搜索功能(多条件组合查询)

  5. 分页查询

  6. 批量操作

  7. 统计功能

  8. 健康检查

  9. 全局异常处理

  10. 完整的日志配置

启动应用后,访问 http://localhost:8080/api/es/users/health 即可测试服务是否正常。所有 API 都提供了完整的错误处理和日志记录。

上面的日期格式有点问题,转换后如下所示:

需要修改 DataInitializer 类!因为方案3将日期字段从 LocalDateTime 改为了 Long 类型(时间戳),所以初始化代码需要相应调整。

修正后的 DataInitializer.java

java

package com.example.esdemo.component;

import com.example.esdemo.entity.UserEs;
import com.example.esdemo.service.UserEsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

@Slf4j
@Component
public class DataInitializer implements CommandLineRunner {

    @Autowired
    private UserEsService userEsService;

    @Override
    public void run(String... args) throws Exception {
        log.info("=== 开始初始化Elasticsearch测试数据 ===");
        
        // 1. 删除旧索引(避免数据冲突)
        log.info("清理旧索引...");
        boolean deleted = userEsService.deleteIndex();
        log.info("索引删除结果: {}", deleted);
        
        Thread.sleep(2000);
        
        // 2. 创建新索引
        log.info("创建新索引...");
        boolean indexCreated = userEsService.createIndex();
        log.info("索引创建结果: {}", indexCreated);
        
        if (!indexCreated) {
            log.error("索引创建失败,停止数据初始化");
            return;
        }
        
        // 等待索引创建完成
        Thread.sleep(3000);
        
        // 3. 检查索引状态
        boolean indexExists = userEsService.indexExists();
        log.info("索引存在状态: {}", indexExists);
        
        if (!indexExists) {
            log.error("索引创建后检查失败,停止数据初始化");
            return;
        }
        
        // 4. 添加测试数据
        log.info("开始添加测试数据...");
        List testUsers = createTestUsers();
        
        try {
            List savedUsers = userEsService.saveAllUsers(testUsers);
            log.info("测试数据添加成功,共添加 {} 个用户", savedUsers.size());
            
            // 5. 等待数据索引完成
            Thread.sleep(2000);
            
            // 6. 验证数据
            long userCount = userEsService.count();
            log.info("数据验证 - 当前用户总数: {}", userCount);
            
            if (userCount == testUsers.size()) {
                log.info("✅ 数据初始化成功!");
                
                // 7. 测试查询操作
                testQueryOperations();
            } else {
                log.warn("⚠️ 数据数量不匹配,期望: {}, 实际: {}", testUsers.size(), userCount);
            }
            
        } catch (Exception e) {
            log.error("❌ 数据初始化失败", e);
            // 即使失败也继续测试查询,可能部分数据写入成功
            testQueryOperations();
        }
        
        log.info("=== Elasticsearch测试数据初始化完成 ===");
    }
    
    /**
     * 创建测试用户数据
     */
    private List createTestUsers() {
        // 使用静态工厂方法创建用户,会自动设置时间戳
        return Arrays.asList(
            UserEs.createUser(1L, "zhangsan", "张三", 25, "zhangsan@example.com", "北京市朝阳区", "13800138001"),
            UserEs.createUser(2L, "lisi", "李四", 30, "lisi@example.com", "上海市浦东新区", "13800138002"),
            UserEs.createUser(3L, "wangwu", "王五", 28, "wangwu@example.com", "广州市天河区", "13800138003"),
            UserEs.createUser(4L, "zhaoliu", "赵六", 35, "zhaoliu@example.com", "深圳市南山区", "13800138004"),
            UserEs.createUser(5L, "sunqi", "孙七", 22, "sunqi@example.com", "杭州市西湖区", "13800138005"),
            UserEs.createUser(6L, "zhouba", "周八", 40, "zhouba@example.com", "成都市武侯区", "13800138006"),
            UserEs.createUser(7L, "wujiu", "吴九", 26, "wujiu@example.com", "武汉市江汉区", "13800138007"),
            UserEs.createUser(8L, "zhengshi", "郑十", 33, "zhengshi@example.com", "南京市鼓楼区", "13800138008")
        );
    }
    
    /**
     * 测试查询操作
     */
    private void testQueryOperations() {
        try {
            log.info("开始测试查询操作...");
            
            // 测试1:根据用户ID查询
            var userOpt = userEsService.findByUserId(2L);
            if (userOpt.isPresent()) {
                UserEs user = userOpt.get();
                log.info("✅ 根据用户ID查询测试成功: {} - {}", user.getUserId(), user.getUsername());
                log.info("   创建时间戳: {}, 转换为时间: {}", 
                        user.getCreateTime(), 
                        user.getCreateTimeAsLocalDateTime());
            } else {
                log.warn("⚠️ 根据用户ID查询测试失败 - 用户ID: 2");
            }
            
            // 测试2:根据用户名查询
            var userOpt2 = userEsService.findByUsername("zhangsan");
            if (userOpt2.isPresent()) {
                UserEs user = userOpt2.get();
                log.info("✅ 根据用户名查询测试成功: {} - {}", user.getUsername(), user.getRealName());
            } else {
                log.warn("⚠️ 根据用户名查询测试失败 - 用户名: zhangsan");
            }
            
            // 测试3:查询所有用户
            List allUsers = userEsService.findAll();
            log.info("✅ 查询所有用户测试成功: {} 条记录", allUsers.size());
            
            // 测试4:根据年龄范围查询
            List ageRangeUsers = userEsService.findByAgeRange(25, 35);
            log.info("✅ 根据年龄范围查询测试成功: {} 条记录", ageRangeUsers.size());
            
            // 测试5:统计用户数量
            long count = userEsService.count();
            log.info("✅ 统计用户数量测试成功: {} 个用户", count);
            
            log.info("所有查询操作测试完成");
            
        } catch (Exception e) {
            log.error("查询测试失败", e);
        }
    }
}

如果还需要手动创建用户数据的补充方法

UserEsService.java - 添加手动创建方法

java

// 在UserEsService类中添加以下方法:

/**
 * 手动创建用户(用于测试和调试)
 */
public UserEs createUserManually(Long userId, String username, String realName, 
                                Integer age, String email, String address, String phone) {
    UserEs user = new UserEs();
    user.setUserId(userId);
    user.setUsername(username);
    user.setRealName(realName);
    user.setAge(age);
    user.setEmail(email);
    user.setAddress(address);
    user.setPhone(phone);
    user.setCreateTime(System.currentTimeMillis());
    user.setUpdateTime(System.currentTimeMillis());
    user.setStatus(true);
    
    return saveUser(user);
}

/**
 * 检查并修复现有数据的日期字段(如果之前有旧数据)
 */
public void fixDateFields() {
    try {
        List allUsers = findAll();
        for (UserEs user : allUsers) {
            // 如果createTime为null,设置为当前时间戳
            if (user.getCreateTime() == null) {
                user.setCreateTime(System.currentTimeMillis());
            }
            // 如果updateTime为null,设置为当前时间戳
            if (user.getUpdateTime() == null) {
                user.setUpdateTime(System.currentTimeMillis());
            }
            // 保存修复后的数据
            userEsRepository.save(user);
        }
        log.info("日期字段修复完成,处理了 {} 条记录", allUsers.size());
    } catch (Exception e) {
        log.error("修复日期字段失败", e);
    }
}

完整的重启和测试步骤

  1. 停止应用(如果正在运行)

  2. 删除Elasticsearch中的旧索引(确保干净的环境)

    bash

    # 通过Kibana或curl删除
    curl -X DELETE "http://localhost:9200/user_es_index"
  3. 启动应用 - DataInitializer会自动:

    • 删除旧索引(如果存在)

    • 创建新索引

    • 插入测试数据

    • 执行查询测试

  4. 验证数据

    bash

    # 检查索引状态
    curl -X GET "http://localhost:8080/api/es/index/status"
    
    # 查询所有用户
    curl -X GET "http://localhost:8080/api/es/users"
    
    # 根据用户ID查询
    curl -X GET "http://localhost:8080/api/es/users/userId/2"

关键修改点总结

  1. 删除了所有对LocalDateTime的直接使用 - 改用Long时间戳

  2. 使用静态工厂方法UserEs.createUser() - 自动设置正确的时间戳

  3. 添加了查询测试 - 在初始化后自动验证数据可查询

  4. 增加了错误处理 - 即使部分失败也能继续测试

这样修改后,日期转换问题应该完全解决,所有查询操作都能正常工作。