Spring Boot 2.5集成Elasticsearch(亲测)

您说得对!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

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.10</version>
    </parent>
    
    <groupId>com.example</groupId>
    <artifactId>es-demo</artifactId>
    <version>1.0.0</version>
    
    <properties>
        <java.version>1.8</java.version>
    </properties>
    
    <dependencies>
        <!-- Spring Boot Starter Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- Spring Data Elasticsearch (Spring Boot 2.5.10 自带 Elasticsearch 7.12 客户端) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
        
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <!-- Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

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<String, Object> 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<UserEs, String> {
    
    // ==================== 基本查询方法 ====================
    
    /**
     * 根据用户名查询(精确匹配)
     */
    Optional<UserEs> findByUsername(String username);
    
    /**
     * 根据邮箱查询(精确匹配)
     */
    Optional<UserEs> findByEmail(String email);
    
    /**
     * 根据手机号查询(精确匹配)
     */
    Optional<UserEs> findByPhone(String phone);
    
    /**
     * 根据用户ID查询
     */
    Optional<UserEs> findByUserId(Long userId);
    
    // ==================== 范围查询方法 ====================
    
    /**
     * 根据年龄范围查询
     */
    List<UserEs> findByAgeBetween(Integer minAge, Integer maxAge);
    
    /**
     * 查询年龄大于指定值的用户
     */
    List<UserEs> findByAgeGreaterThan(Integer age);
    
    /**
     * 查询年龄小于指定值的用户
     */
    List<UserEs> findByAgeLessThan(Integer age);
    
    // ==================== 模糊查询方法 ====================
    
    /**
     * 根据真实姓名模糊查询
     */
    List<UserEs> findByRealNameContaining(String realName);
    
    /**
     * 根据地址模糊查询
     */
    List<UserEs> findByAddressContaining(String address);
    
    /**
     * 根据地址模糊查询并分页
     */
    Page<UserEs> findByAddressContaining(String address, Pageable pageable);
    
    // ==================== 状态查询方法 ====================
    
    /**
     * 根据状态查询用户
     */
    List<UserEs> findByStatus(Boolean status);
    
    /**
     * 根据状态查询用户并分页
     */
    Page<UserEs> findByStatus(Boolean status, Pageable pageable);
    
    // ==================== 组合查询方法 ====================
    
    /**
     * 根据用户名和年龄查询
     */
    List<UserEs> findByUsernameAndAge(String username, Integer age);
    
    /**
     * 根据状态和年龄范围查询
     */
    List<UserEs> findByStatusAndAgeBetween(Boolean status, Integer minAge, Integer maxAge);
    
    /**
     * 根据真实姓名和状态查询
     */
    List<UserEs> findByRealNameContainingAndStatus(String realName, Boolean status);
    
    // ==================== 排序查询方法 ====================
    
    /**
     * 根据创建时间倒序查询所有用户
     */
    List<UserEs> findAllByOrderByCreateTimeDesc();
    
    /**
     * 根据年龄升序查询所有用户
     */
    List<UserEs> 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<String, Object> getIndexInfo() {
        Map<String, Object> 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<UserEs> saveAllUsers(List<UserEs> 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<UserEs> savedUsers = userEsRepository.saveAll(userEsList);
            List<UserEs> 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<UserEs> findById(String id) {
        try {
            return userEsRepository.findById(id);
        } catch (Exception e) {
            log.error("根据ID查询用户失败, ID: {}", id, e);
            return Optional.empty();
        }
    }

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

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

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

    /**
     * 分页查询所有用户
     */
    public Page<UserEs> 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<String, Object> updates) {
        try {
            Optional<UserEs> 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<String, Object>) 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<UserEs> 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<String> ids) {
        try {
            List<UserEs> 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<UserEs> 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<UserEs> findByStatus(Boolean status) {
        try {
            return userEsRepository.findByStatus(status);
        } catch (Exception e) {
            log.error("根据状态查询用户失败, status: {}", status, e);
            return Collections.emptyList();
        }
    }

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

    /**
     * 复杂搜索:多条件组合查询
     */
    public List<UserEs> 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<Map<String, Object>> createIndex() {
        log.info("请求创建索引");
        try {
            boolean success = userEsService.createIndex();
            Map<String, Object> 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<Map<String, Object>> deleteIndex() {
        log.info("请求删除索引");
        try {
            boolean success = userEsService.deleteIndex();
            Map<String, Object> 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<Map<String, Object>> indexExists() {
        log.info("请求检查索引是否存在");
        try {
            boolean exists = userEsService.indexExists();
            Map<String, Object> 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<Map<String, Object>> getIndexInfo() {
        log.info("请求获取索引信息");
        try {
            Map<String, Object> indexInfo = userEsService.getIndexInfo();
            Map<String, Object> 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<Map<String, Object>> refreshIndex() {
        log.info("请求刷新索引");
        try {
            userEsService.refreshIndex();
            Map<String, Object> 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<Map<String, Object>> addUser(@RequestBody UserEs userEs) {
        log.info("请求添加用户: {}", userEs.getUsername());
        try {
            UserEs savedUser = userEsService.saveUser(userEs);
            Map<String, Object> 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<Map<String, Object>> batchAddUsers(@RequestBody List<UserEs> userEsList) {
        log.info("请求批量添加用户, 数量: {}", userEsList.size());
        try {
            List<UserEs> savedUsers = userEsService.saveAllUsers(userEsList);
            Map<String, Object> 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<Map<String, Object>> getUserById(@PathVariable String id) {
        log.info("请求根据ID查询用户: {}", id);
        try {
            Optional<UserEs> userOpt = userEsService.findById(id);
            if (userOpt.isPresent()) {
                Map<String, Object> 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<Map<String, Object>> getUserByUserId(@PathVariable Long userId) {
        log.info("请求根据用户ID查询: {}", userId);
        try {
            Optional<UserEs> userOpt = userEsService.findByUserId(userId);
            if (userOpt.isPresent()) {
                Map<String, Object> 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<Map<String, Object>> getUserByUsername(@PathVariable String username) {
        log.info("请求根据用户名查询: {}", username);
        try {
            Optional<UserEs> userOpt = userEsService.findByUsername(username);
            if (userOpt.isPresent()) {
                Map<String, Object> 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<Map<String, Object>> getAllUsers() {
        log.info("请求查询所有用户");
        try {
            List<UserEs> users = userEsService.findAll();
            Map<String, Object> 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<Map<String, Object>> getAllUsersWithPage(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size) {
        log.info("请求分页查询用户, page: {}, size: {}", page, size);
        try {
            Page<UserEs> userPage = userEsService.findAllWithPage(page, size);
            Map<String, Object> 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<Map<String, Object>> updateUser(@RequestBody UserEs userEs) {
        log.info("请求更新用户: {}", userEs.getId());
        try {
            UserEs updatedUser = userEsService.updateUser(userEs);
            Map<String, Object> 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<Map<String, Object>> partialUpdateUser(
            @PathVariable String id, 
            @RequestBody Map<String, Object> updates) {
        log.info("请求部分更新用户: {}, updates: {}", id, updates);
        try {
            UserEs updatedUser = userEsService.partialUpdate(id, updates);
            Map<String, Object> 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<Map<String, Object>> deleteUser(@PathVariable String id) {
        log.info("请求删除用户: {}", id);
        try {
            boolean success = userEsService.deleteById(id);
            if (success) {
                Map<String, Object> 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<Map<String, Object>> deleteUserByUsername(@PathVariable String username) {
        log.info("请求根据用户名删除用户: {}", username);
        try {
            boolean success = userEsService.deleteByUsername(username);
            if (success) {
                Map<String, Object> 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<Map<String, Object>> getUsersByAgeRange(
            @RequestParam Integer minAge,
            @RequestParam Integer maxAge) {
        log.info("请求根据年龄范围查询, minAge: {}, maxAge: {}", minAge, maxAge);
        try {
            List<UserEs> users = userEsService.findByAgeRange(minAge, maxAge);
            Map<String, Object> 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<Map<String, Object>> getUsersByStatus(@PathVariable Boolean status) {
        log.info("请求根据状态查询用户: {}", status);
        try {
            List<UserEs> users = userEsService.findByStatus(status);
            Map<String, Object> 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<Map<String, Object>> getUsersByAddress(@PathVariable String address) {
        log.info("请求根据地址查询用户: {}", address);
        try {
            List<UserEs> users = userEsService.findByAddress(address);
            Map<String, Object> 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<Map<String, Object>> 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<UserEs> users = userEsService.complexSearch(keyword, minAge, maxAge, address, status);
            Map<String, Object> 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<Map<String, Object>> getUserCount() {
        log.info("请求统计用户总数");
        try {
            long count = userEsService.count();
            Map<String, Object> 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<Map<String, Object>> countByStatus(@PathVariable Boolean status) {
        log.info("请求根据状态统计用户数量: {}", status);
        try {
            long count = userEsService.countByStatus(status);
            Map<String, Object> 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<Map<String, Object>> checkUsernameExists(@PathVariable String username) {
        log.info("请求检查用户名是否存在: {}", username);
        try {
            boolean exists = userEsService.existsByUsername(username);
            Map<String, Object> 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<Map<String, Object>> checkEmailExists(@PathVariable String email) {
        log.info("请求检查邮箱是否存在: {}", email);
        try {
            boolean exists = userEsService.existsByEmail(email);
            Map<String, Object> 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<Map<String, Object>> healthCheck() {
        log.info("请求健康检查");
        try {
            boolean indexExists = userEsService.indexExists();
            long userCount = userEsService.count();
            
            Map<String, Object> healthInfo = new HashMap<>();
            healthInfo.put("status", "UP");
            healthInfo.put("indexExists", indexExists);
            healthInfo.put("userCount", userCount);
            healthInfo.put("timestamp", System.currentTimeMillis());
            
            Map<String, Object> result = createSuccessResult(true, "服务正常");
            result.put("health", healthInfo);
            
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            log.error("健康检查异常", e);
            Map<String, Object> healthInfo = new HashMap<>();
            healthInfo.put("status", "DOWN");
            healthInfo.put("error", e.getMessage());
            healthInfo.put("timestamp", System.currentTimeMillis());
            
            Map<String, Object> result = createErrorResult("服务异常");
            result.put("health", healthInfo);
            
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }

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

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

    private Map<String, Object> createErrorResult(String message) {
        Map<String, Object> 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<UserEs> 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<UserEs> 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<Map<String, Object>> handleException(Exception e) {
        log.error("系统异常: ", e);
        
        Map<String, Object> 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<Map<String, Object>> handleRuntimeException(RuntimeException e) {
        log.error("运行时异常: ", e);
        
        Map<String, Object> 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<Map<String, Object>> handleIllegalArgumentException(IllegalArgumentException e) {
        log.error("参数异常: ", e);
        
        Map<String, Object> 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

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    
    <property name="LOG_PATH" value="logs"/>
    <property name="LOG_FILE" value="es-demo"/>
    
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/${LOG_FILE}.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/${LOG_FILE}-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>30</maxHistory>
            <totalSizeCap>3GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
    </root>
    
    <logger name="com.example.esdemo" level="DEBUG"/>
    <logger name="org.springframework.data.elasticsearch" level="INFO"/>
    <logger name="org.elasticsearch" level="WARN"/>
</configuration>

完整项目结构回顾

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<UserEs> testUsers = createTestUsers();
        
        try {
            List<UserEs> 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<UserEs> 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<UserEs> allUsers = userEsService.findAll();
            log.info("✅ 查询所有用户测试成功: {} 条记录", allUsers.size());
            
            // 测试4:根据年龄范围查询
            List<UserEs> 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<UserEs> 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. 增加了错误处理 - 即使部分失败也能继续测试

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

相关推荐
何中应5 小时前
CentOS安装Jenkins
linux·centos·jenkins
Elastic 中国社区官方博客5 小时前
AutoOps:简化自管理 Elasticsearch 的旅程
大数据·人工智能·elasticsearch·搜索引擎·全文检索
ruleslol6 小时前
SpringBoot12-JSON Web Token(JWT)
spring boot
Terio_my6 小时前
Spring Boot 缓存集成实践
spring boot·后端·缓存
Lin_Aries_04218 小时前
基于 CI/CD 平台将应用程序自动部署到 Kubernetes 集群
运维·ci/cd·docker·云原生·容器·kubernetes·jenkins
Terio_my9 小时前
Elasticsearch 索引创建与文档管理
大数据·elasticsearch·jenkins
JIngJaneIL10 小时前
图书馆自习室|基于SSM的图书馆自习室座位预约小程序设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·图书馆自习室
不要再敲了10 小时前
SSM框架下的redis使用以及token认证
数据库·spring boot·redis·缓存·mybatis
Terio_my11 小时前
Spring Boot 集成 EHCache 缓存解决方案
spring boot·spring·缓存