Compare commits
3 Commits
6cc5c06879
...
4e00542371
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e00542371 | |||
| 3437c2bff4 | |||
| c216ca4c63 |
1
.idea/compiler.xml
generated
1
.idea/compiler.xml
generated
@@ -27,6 +27,7 @@
|
|||||||
<module name="han-note-data-align" />
|
<module name="han-note-data-align" />
|
||||||
<module name="han-note-kv-api" />
|
<module name="han-note-kv-api" />
|
||||||
<module name="han-note-note-biz" />
|
<module name="han-note-note-biz" />
|
||||||
|
<module name="han-note-search" />
|
||||||
<module name="han-note-user-relation-api" />
|
<module name="han-note-user-relation-api" />
|
||||||
<module name="han-note-user-api" />
|
<module name="han-note-user-api" />
|
||||||
<module name="han-note-user-biz" />
|
<module name="han-note-user-biz" />
|
||||||
|
|||||||
2
.idea/encodings.xml
generated
2
.idea/encodings.xml
generated
@@ -37,6 +37,8 @@
|
|||||||
<file url="file://$PROJECT_DIR$/han-note-oss/han-note-oss-biz/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/han-note-oss/han-note-oss-biz/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/han-note-oss/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/han-note-oss/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/han-note-oss/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/han-note-oss/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-search/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/han-note-search/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/han-note-user-relation/han-note-user-relation-api/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/han-note-user-relation/han-note-user-relation-api/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/han-note-user-relation/han-note-user-relation-api/src/main/resources" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/han-note-user-relation/han-note-user-relation-api/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/han-note-user-relation/han-note-user-relation-biz/src/main/java" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/han-note-user-relation/han-note-user-relation-biz/src/main/java" charset="UTF-8" />
|
||||||
|
|||||||
77
han-note-search/pom.xml
Normal file
77
han-note-search/pom.xml
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<!-- 指定父项目 -->
|
||||||
|
<parent>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>han-note</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<!-- 指定打包方式 -->
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<artifactId>han-note-search</artifactId>
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<description>搜索服务</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>hanserwei-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 业务接口日志组件 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>hanserwei-spring-boot-starter-biz-operationlog</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Jackson 组件 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.hanserwei</groupId>
|
||||||
|
<artifactId>hanserwei-spring-boot-starter-jackson</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-bootstrap</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 服务注册发现 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Elasticsearch 分布式搜索引擎 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.elastic.clients</groupId>
|
||||||
|
<artifactId>elasticsearch-java</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.elasticsearch.client</groupId>
|
||||||
|
<artifactId>elasticsearch-rest-client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.hanserwei.hannote.search;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class HannoteSearchApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(HannoteSearchApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.hanserwei.hannote.search.config;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||||
|
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
|
||||||
|
import co.elastic.clients.transport.ElasticsearchTransport;
|
||||||
|
import co.elastic.clients.transport.rest_client.RestClientTransport;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
|
import org.elasticsearch.client.RestClient;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class ElasticsearchConfig {
|
||||||
|
|
||||||
|
@Value("${elasticsearch.host}")
|
||||||
|
private String host;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ElasticsearchClient elasticsearchClient() {
|
||||||
|
// 1. 创建底层 RestClient(低级客户端)
|
||||||
|
RestClient restClient = RestClient
|
||||||
|
.builder(HttpHost.create(host))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 2. 创建 JSON 映射器
|
||||||
|
ObjectMapper mapper = JsonMapper.builder().build();
|
||||||
|
JacksonJsonpMapper jsonpMapper = new JacksonJsonpMapper(mapper);
|
||||||
|
|
||||||
|
// 3. 构建传输层
|
||||||
|
ElasticsearchTransport transport = new RestClientTransport(restClient, jsonpMapper);
|
||||||
|
|
||||||
|
// 4. 创建高层次的 Elasticsearch 客户端
|
||||||
|
return new ElasticsearchClient(transport);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.hanserwei.hannote.search.controller;
|
||||||
|
|
||||||
|
import com.hanserwei.framework.biz.operationlog.aspect.ApiOperationLog;
|
||||||
|
import com.hanserwei.framework.common.response.PageResponse;
|
||||||
|
import com.hanserwei.hannote.search.model.vo.SearchUserReqVO;
|
||||||
|
import com.hanserwei.hannote.search.model.vo.SearchUserRspVO;
|
||||||
|
import com.hanserwei.hannote.search.service.UserService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/search")
|
||||||
|
@Slf4j
|
||||||
|
public class UserController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
@PostMapping("/user")
|
||||||
|
@ApiOperationLog(description = "搜索用户")
|
||||||
|
public PageResponse<SearchUserRspVO> searchUser(@RequestBody @Validated SearchUserReqVO searchUserReqVO) {
|
||||||
|
return userService.searchUser(searchUserReqVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.hanserwei.hannote.search.enums;
|
||||||
|
|
||||||
|
import com.hanserwei.framework.common.exception.BaseExceptionInterface;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum ResponseCodeEnum implements BaseExceptionInterface {
|
||||||
|
|
||||||
|
// ----------- 通用异常状态码 -----------
|
||||||
|
SYSTEM_ERROR("SEARCH-10000", "出错啦,后台小哥正在努力修复中..."),
|
||||||
|
PARAM_NOT_VALID("SEARCH-10001", "参数错误"),
|
||||||
|
|
||||||
|
// ----------- 业务异常状态码 -----------
|
||||||
|
;
|
||||||
|
|
||||||
|
// 异常码
|
||||||
|
private final String errorCode;
|
||||||
|
// 错误信息
|
||||||
|
private final String errorMsg;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package com.hanserwei.hannote.search.exception;
|
||||||
|
|
||||||
|
import com.hanserwei.framework.common.exception.ApiException;
|
||||||
|
import com.hanserwei.framework.common.response.Response;
|
||||||
|
import com.hanserwei.hannote.search.enums.ResponseCodeEnum;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@SuppressWarnings("LoggingSimilarMessage")
|
||||||
|
@ControllerAdvice
|
||||||
|
@Slf4j
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 捕获自定义业务异常
|
||||||
|
*
|
||||||
|
* @return Response.fail(e)
|
||||||
|
*/
|
||||||
|
@ExceptionHandler({ApiException.class})
|
||||||
|
@ResponseBody
|
||||||
|
public Response<Object> handleApiException(HttpServletRequest request, ApiException e) {
|
||||||
|
log.warn("{} request fail, errorCode: {}, errorMessage: {}", request.getRequestURI(), e.getErrorCode(), e.getErrorMsg());
|
||||||
|
return Response.fail(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 捕获参数校验异常
|
||||||
|
*
|
||||||
|
* @return Response.fail(errorCode, errorMessage)
|
||||||
|
*/
|
||||||
|
@ExceptionHandler({MethodArgumentNotValidException.class})
|
||||||
|
@ResponseBody
|
||||||
|
public Response<Object> handleMethodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException e) {
|
||||||
|
// 参数错误异常码
|
||||||
|
String errorCode = ResponseCodeEnum.PARAM_NOT_VALID.getErrorCode();
|
||||||
|
|
||||||
|
// 获取 BindingResult
|
||||||
|
BindingResult bindingResult = e.getBindingResult();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
// 获取校验不通过的字段,并组合错误信息,格式为: email 邮箱格式不正确, 当前值: '123124qq.com';
|
||||||
|
Optional.of(bindingResult.getFieldErrors()).ifPresent(errors -> {
|
||||||
|
errors.forEach(error ->
|
||||||
|
sb.append(error.getField())
|
||||||
|
.append(" ")
|
||||||
|
.append(error.getDefaultMessage())
|
||||||
|
.append(", 当前值: '")
|
||||||
|
.append(error.getRejectedValue())
|
||||||
|
.append("'; ")
|
||||||
|
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 错误信息
|
||||||
|
String errorMessage = sb.toString();
|
||||||
|
|
||||||
|
log.warn("{} request error, errorCode: {}, errorMessage: {}", request.getRequestURI(), errorCode, errorMessage);
|
||||||
|
|
||||||
|
return Response.fail(errorCode, errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 捕获 guava 参数校验异常
|
||||||
|
*
|
||||||
|
* @return Response.fail(ResponseCodeEnum.PARAM_NOT_VALID)
|
||||||
|
*/
|
||||||
|
@ExceptionHandler({IllegalArgumentException.class})
|
||||||
|
@ResponseBody
|
||||||
|
public Response<Object> handleIllegalArgumentException(HttpServletRequest request, IllegalArgumentException e) {
|
||||||
|
// 参数错误异常码
|
||||||
|
String errorCode = ResponseCodeEnum.PARAM_NOT_VALID.getErrorCode();
|
||||||
|
|
||||||
|
// 错误信息
|
||||||
|
String errorMessage = e.getMessage();
|
||||||
|
|
||||||
|
log.warn("{} request error, errorCode: {}, errorMessage: {}", request.getRequestURI(), errorCode, errorMessage);
|
||||||
|
|
||||||
|
return Response.fail(errorCode, errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 其他类型异常
|
||||||
|
*
|
||||||
|
* @param request 请求
|
||||||
|
* @param e 异常
|
||||||
|
* @return Response.fail(ResponseCodeEnum.SYSTEM_ERROR)
|
||||||
|
*/
|
||||||
|
@ExceptionHandler({Exception.class})
|
||||||
|
@ResponseBody
|
||||||
|
public Response<Object> handleOtherException(HttpServletRequest request, Exception e) {
|
||||||
|
log.error("{} request error, ", request.getRequestURI(), e);
|
||||||
|
return Response.fail(ResponseCodeEnum.SYSTEM_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.hanserwei.hannote.search.index;
|
||||||
|
|
||||||
|
public class UserIndex {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 索引名称
|
||||||
|
*/
|
||||||
|
public static final String NAME = "user";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
public static final String FIELD_USER_ID = "id";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 昵称
|
||||||
|
*/
|
||||||
|
public static final String FIELD_USER_NICKNAME = "nickname";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 头像
|
||||||
|
*/
|
||||||
|
public static final String FIELD_USER_AVATAR = "avatar";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小憨书ID
|
||||||
|
*/
|
||||||
|
public static final String FIELD_USER_HAN_NOTE_ID = "han_note_id";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发布笔记总数
|
||||||
|
*/
|
||||||
|
public static final String FIELD_USER_NOTE_TOTAL = "note_total";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 粉丝总数
|
||||||
|
*/
|
||||||
|
public static final String FIELD_USER_FANS_TOTAL = "fans_total";
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.hanserwei.hannote.search.model.vo;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class SearchUserReqVO {
|
||||||
|
|
||||||
|
@NotBlank(message = "搜索关键词不能为空")
|
||||||
|
private String keyword;
|
||||||
|
|
||||||
|
@Min(value = 1, message = "页码不能小于 1")
|
||||||
|
private Integer pageNo = 1; // 默认值为第一页
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.hanserwei.hannote.search.model.vo;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class SearchUserRspVO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
@JsonProperty("id")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 昵称
|
||||||
|
*/
|
||||||
|
private String nickname;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 头像
|
||||||
|
*/
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小憨书ID
|
||||||
|
*/
|
||||||
|
@JsonProperty("han_note_id")
|
||||||
|
private String hanNoteId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 笔记发布总数
|
||||||
|
*/
|
||||||
|
@JsonProperty("note_total")
|
||||||
|
private Integer noteTotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 粉丝总数
|
||||||
|
*/
|
||||||
|
@JsonProperty("fans_total")
|
||||||
|
private Integer fansTotal;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.hanserwei.hannote.search.service;
|
||||||
|
|
||||||
|
import com.hanserwei.framework.common.response.PageResponse;
|
||||||
|
import com.hanserwei.hannote.search.model.vo.SearchUserReqVO;
|
||||||
|
import com.hanserwei.hannote.search.model.vo.SearchUserRspVO;
|
||||||
|
|
||||||
|
public interface UserService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索用户
|
||||||
|
*
|
||||||
|
* @param searchUserReqVO 搜索用户请求
|
||||||
|
* @return 搜索用户响应
|
||||||
|
*/
|
||||||
|
PageResponse<SearchUserRspVO> searchUser(SearchUserReqVO searchUserReqVO);
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package com.hanserwei.hannote.search.service.impl;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||||
|
import co.elastic.clients.elasticsearch._types.SortOrder;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.TextQueryType;
|
||||||
|
import co.elastic.clients.elasticsearch.core.SearchRequest;
|
||||||
|
import co.elastic.clients.elasticsearch.core.SearchResponse;
|
||||||
|
import co.elastic.clients.elasticsearch.core.search.Hit;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.hanserwei.framework.common.response.PageResponse;
|
||||||
|
import com.hanserwei.hannote.search.index.UserIndex;
|
||||||
|
import com.hanserwei.hannote.search.model.vo.SearchUserReqVO;
|
||||||
|
import com.hanserwei.hannote.search.model.vo.SearchUserRspVO;
|
||||||
|
import com.hanserwei.hannote.search.service.UserService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class UserServiceImpl implements UserService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ElasticsearchClient client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 SearchUserRspVO
|
||||||
|
*
|
||||||
|
* @param hit 搜索结果
|
||||||
|
* @return SearchUserRspVO
|
||||||
|
*/
|
||||||
|
private static SearchUserRspVO getSearchUserRspVO(Hit<SearchUserRspVO> hit) {
|
||||||
|
SearchUserRspVO searchUserRspVO = new SearchUserRspVO();
|
||||||
|
|
||||||
|
SearchUserRspVO source = hit.source();
|
||||||
|
if (source != null) {
|
||||||
|
searchUserRspVO.setUserId(source.getUserId());
|
||||||
|
searchUserRspVO.setNickname(source.getNickname());
|
||||||
|
searchUserRspVO.setAvatar(source.getAvatar());
|
||||||
|
searchUserRspVO.setHanNoteId(source.getHanNoteId());
|
||||||
|
searchUserRspVO.setNoteTotal(source.getNoteTotal());
|
||||||
|
searchUserRspVO.setFansTotal(source.getFansTotal());
|
||||||
|
}
|
||||||
|
return searchUserRspVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageResponse<SearchUserRspVO> searchUser(SearchUserReqVO searchUserReqVO) {
|
||||||
|
// 查询关键字
|
||||||
|
String keyword = searchUserReqVO.getKeyword();
|
||||||
|
// 当前页码
|
||||||
|
Integer pageNo = searchUserReqVO.getPageNo();
|
||||||
|
|
||||||
|
int pageSize = 10;
|
||||||
|
|
||||||
|
// 构建SearchRequest,指定索引
|
||||||
|
SearchRequest searchRequest = new SearchRequest.Builder()
|
||||||
|
.index(UserIndex.NAME)
|
||||||
|
.query(query -> query
|
||||||
|
.multiMatch(multiMatch -> multiMatch
|
||||||
|
.query(keyword)
|
||||||
|
.fields(UserIndex.FIELD_USER_NICKNAME, UserIndex.FIELD_USER_HAN_NOTE_ID)
|
||||||
|
.type(TextQueryType.PhrasePrefix)))
|
||||||
|
.sort(sort -> sort
|
||||||
|
.field(filedSort -> filedSort.field(UserIndex.FIELD_USER_FANS_TOTAL).order(SortOrder.Desc)))
|
||||||
|
.from((pageNo - 1) * pageSize)
|
||||||
|
.size(pageSize)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 返参 VO 集合
|
||||||
|
List<SearchUserRspVO> searchUserRspVOS = null;
|
||||||
|
// 总文档数,默认为 0
|
||||||
|
long total = 0;
|
||||||
|
try {
|
||||||
|
log.info("==> SearchRequest:{}", searchRequest);
|
||||||
|
|
||||||
|
// 执行查询请求
|
||||||
|
SearchResponse<SearchUserRspVO> searchResponse = client.search(searchRequest, SearchUserRspVO.class);
|
||||||
|
|
||||||
|
searchUserRspVOS = Lists.newArrayList();
|
||||||
|
|
||||||
|
// 处理搜索结果
|
||||||
|
List<Hit<SearchUserRspVO>> hits = searchResponse.hits().hits();
|
||||||
|
if (searchResponse.hits().total() != null) {
|
||||||
|
total = searchResponse.hits().total().value();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Hit<SearchUserRspVO> hit : hits) {
|
||||||
|
log.info("==> 文档数据: {}", hit.toString());
|
||||||
|
if (hit.source() != null) {
|
||||||
|
SearchUserRspVO searchUserRspVO = getSearchUserRspVO(hit);
|
||||||
|
searchUserRspVOS.add(searchUserRspVO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("==> 查询 Elasticsearch 异常: ", e);
|
||||||
|
}
|
||||||
|
return PageResponse.success(searchUserRspVOS, pageNo, total);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
han-note-search/src/main/resources/application.yml
Normal file
9
han-note-search/src/main/resources/application.yml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
server:
|
||||||
|
port: 8092 # 项目启动的端口
|
||||||
|
|
||||||
|
spring:
|
||||||
|
profiles:
|
||||||
|
active: dev # 默认激活 dev 本地开发环境
|
||||||
|
servlet:
|
||||||
|
multipart:
|
||||||
|
max-file-size: 20MB # 单个文件最大大小
|
||||||
19
han-note-search/src/main/resources/bootstrap.yml
Normal file
19
han-note-search/src/main/resources/bootstrap.yml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: han-note-search # 应用名称
|
||||||
|
profiles:
|
||||||
|
active: dev # 默认激活 dev 本地开发环境
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
enabled: true # 启用服务发现
|
||||||
|
group: DEFAULT_GROUP # 所属组
|
||||||
|
namespace: han-note # 命名空间
|
||||||
|
server-addr: 127.0.0.1:8848 # 指定 Nacos 配置中心的服务器地址
|
||||||
|
config:
|
||||||
|
server-addr: http://127.0.0.1:8848 # 指定 Nacos 配置中心的服务器地址
|
||||||
|
prefix: ${spring.application.name} # 配置 Data Id 前缀,这里使用应用名称作为前缀
|
||||||
|
group: DEFAULT_GROUP # 所属组
|
||||||
|
namespace: han-note # 命名空间
|
||||||
|
file-extension: yaml # 配置文件格式
|
||||||
|
refresh-enabled: true # 是否开启动态刷新
|
||||||
58
han-note-search/src/main/resources/logback-spring.xml
Normal file
58
han-note-search/src/main/resources/logback-spring.xml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<configuration>
|
||||||
|
<!-- 引用 Spring Boot 的 logback 基础配置 -->
|
||||||
|
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||||
|
|
||||||
|
<!-- 应用名称 -->
|
||||||
|
<property scope="context" name="appName" value="search"/>
|
||||||
|
<!-- 自定义日志输出路径,以及日志名称前缀 -->
|
||||||
|
<property name="LOG_FILE" value="./logs/${appName}.%d{yyyy-MM-dd}"/>
|
||||||
|
<!-- 每行日志输出的格式 -->
|
||||||
|
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
|
||||||
|
|
||||||
|
<!-- 文件输出 -->
|
||||||
|
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<!-- 日志文件的命名格式 -->
|
||||||
|
<fileNamePattern>${LOG_FILE}-%i.log</fileNamePattern>
|
||||||
|
<!-- 保留 30 天的日志文件 -->
|
||||||
|
<maxHistory>30</maxHistory>
|
||||||
|
<!-- 单个日志文件最大大小 -->
|
||||||
|
<maxFileSize>10MB</maxFileSize>
|
||||||
|
<!-- 日志文件的总大小,0 表示不限制 -->
|
||||||
|
<totalSizeCap>0</totalSizeCap>
|
||||||
|
<!-- 重启服务时,是否清除历史日志,不推荐清理 -->
|
||||||
|
<cleanHistoryOnStart>false</cleanHistoryOnStart>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
<pattern>${LOG_PATTERN}</pattern>
|
||||||
|
<charset>UTF-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 异步写入日志,提升性能 -->
|
||||||
|
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
|
||||||
|
<!-- 是否丢弃日志, 0 表示不丢弃。默认情况下,如果队列满 80%, 会丢弃 TRACE、DEBUG、INFO 级别的日志 -->
|
||||||
|
<discardingThreshold>0</discardingThreshold>
|
||||||
|
<!-- 队列大小。默认值为 256 -->
|
||||||
|
<queueSize>256</queueSize>
|
||||||
|
<appender-ref ref="FILE"/>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 本地 dev 开发环境 -->
|
||||||
|
<springProfile name="dev">
|
||||||
|
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="CONSOLE"/> <!-- 输出控制台日志 -->
|
||||||
|
<appender-ref ref="ASYNC_FILE"/> <!-- 打印日志到文件中。PS: 本地环境下,如果不想打印日志到文件,可注释掉此行 -->
|
||||||
|
</root>
|
||||||
|
</springProfile>
|
||||||
|
|
||||||
|
<!-- 其它环境 -->
|
||||||
|
<springProfile name="prod">
|
||||||
|
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="ASYNC_FILE"/> <!-- 生产环境下,仅打印日志到文件中 -->
|
||||||
|
</root>
|
||||||
|
</springProfile>
|
||||||
|
|
||||||
|
</configuration>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.hanserwei.hannote.search;
|
||||||
|
|
||||||
|
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||||
|
import co.elastic.clients.elasticsearch._types.SortOrder;
|
||||||
|
import co.elastic.clients.elasticsearch.core.SearchResponse;
|
||||||
|
import com.hanserwei.hannote.search.model.vo.SearchUserRspVO;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
public class ElasticsearchClientTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ElasticsearchClient client;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SneakyThrows
|
||||||
|
public void testClient() {
|
||||||
|
SearchResponse<SearchUserRspVO> response = client.search(s -> s
|
||||||
|
.index("user")
|
||||||
|
.query(q -> q
|
||||||
|
.multiMatch(mm -> mm
|
||||||
|
.query("Han")
|
||||||
|
.fields("nickname", "han_note_id")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.sort(so -> so
|
||||||
|
.field(f -> f.field("fans_total").order(SortOrder.Desc))
|
||||||
|
)
|
||||||
|
.from(0)
|
||||||
|
.size(10),
|
||||||
|
SearchUserRspVO.class
|
||||||
|
);
|
||||||
|
response.hits().hits().forEach(hit -> System.out.println(hit.source()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -228,4 +228,63 @@ Authorization: Bearer {{otherToken}}
|
|||||||
|
|
||||||
{
|
{
|
||||||
"id": 1977249693272375330
|
"id": 1977249693272375330
|
||||||
}
|
}
|
||||||
|
|
||||||
|
### 创建索引
|
||||||
|
PUT http://localhost:9200/note
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
### 创建文档
|
||||||
|
PUT http://localhost:9200/note/_doc/1
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"title": "夏日旅游攻略",
|
||||||
|
"content": "去海边玩耍的必备攻略,推荐三亚和青岛。",
|
||||||
|
"create_time": "2024-08-16 16:49:35"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 创建文档
|
||||||
|
PUT http://localhost:9200/note/_doc/2
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"title": "美食笔记",
|
||||||
|
"content": "推荐几家成都的火锅店,真的很辣很过瘾!",
|
||||||
|
"create_time": "2024-11-02 17:00:36"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 创建文档
|
||||||
|
PUT http://localhost:9200/note/_doc/3
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"title": "秋季穿搭指南",
|
||||||
|
"content": "分享适合秋天的服装搭配技巧,风衣必不可少。",
|
||||||
|
"create_time": "2024-11-03 16:59:58"
|
||||||
|
}
|
||||||
|
|
||||||
|
### 搜索文档
|
||||||
|
GET http://localhost:9200/note/_search
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"query": {
|
||||||
|
"multi_match": {
|
||||||
|
"query": "攻略",
|
||||||
|
"fields": [
|
||||||
|
"title",
|
||||||
|
"content"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
### 搜索用户(搜索服务)
|
||||||
|
POST http://localhost:8092/search/user
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"keyword": "憨",
|
||||||
|
"pageNo": 1
|
||||||
|
}
|
||||||
|
|||||||
15
pom.xml
15
pom.xml
@@ -23,6 +23,7 @@
|
|||||||
<module>han-note-user-relation</module>
|
<module>han-note-user-relation</module>
|
||||||
<module>han-note-count</module>
|
<module>han-note-count</module>
|
||||||
<module>han-note-data-align</module>
|
<module>han-note-data-align</module>
|
||||||
|
<module>han-note-search</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
@@ -67,6 +68,8 @@
|
|||||||
<rocketmq-client.version>5.3.2</rocketmq-client.version>
|
<rocketmq-client.version>5.3.2</rocketmq-client.version>
|
||||||
<buffertrigger.version>0.2.21</buffertrigger.version>
|
<buffertrigger.version>0.2.21</buffertrigger.version>
|
||||||
<xxl-job.version>3.2.0</xxl-job.version>
|
<xxl-job.version>3.2.0</xxl-job.version>
|
||||||
|
<elasticsearch-java.version>9.2.0</elasticsearch-java.version>
|
||||||
|
<elasticsearch.version>9.2.0</elasticsearch.version>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -300,7 +303,17 @@
|
|||||||
<artifactId>xxl-job-core</artifactId>
|
<artifactId>xxl-job-core</artifactId>
|
||||||
<version>${xxl-job.version}</version>
|
<version>${xxl-job.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- Elasticsearch 分布式搜索引擎 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>co.elastic.clients</groupId>
|
||||||
|
<artifactId>elasticsearch-java</artifactId>
|
||||||
|
<version>${elasticsearch-java.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.elasticsearch.client</groupId>
|
||||||
|
<artifactId>elasticsearch-rest-client</artifactId>
|
||||||
|
<version>${elasticsearch.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user