# xpass-search
**Repository Path**: sxran/xpass-search
## Basic Information
- **Project Name**: xpass-search
- **Description**: 通用的搜索模块
- **Primary Language**: Unknown
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2025-10-21
- **Last Updated**: 2025-11-05
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 芋道搜索模块 (yudao-module-search)
## 📖 项目介绍
芋道搜索模块是一个通用的搜索服务模块,基于芋道微服务架构设计,提供统一的搜索API接口,支持多种搜索引擎的适配。通过适配器模式,可以轻松切换不同的搜索引擎实现,如 ZincSearch、Elasticsearch、OpenSearch、MeiliSearch 等。
## ✨ 核心特性
- **🔌 多引擎适配**: 支持多种搜索引擎,通过配置即可切换
- **🚀 高性能**: 基于 Spring Boot 3.x + WebFlux 异步架构
- **📊 完整功能**: 支持索引管理、文档管理、搜索查询、批量操作等
- **🏢 多租户隔离**: 租户级别的索引物理隔离,确保数据完全隔离
- **🌍 星球过滤**: 同租户内通过星球ID字段实现灵活的数据过滤
- **🔒 安全可靠**: 集成芋道安全框架,支持租户和星球权限控制
- **📝 API 友好**: 提供 RPC 接口和 HTTP REST API
- **🧪 测试完备**: 完整的单元测试覆盖
- **📚 文档齐全**: 详细的 API 文档和使用说明
## 🏗️ 架构设计
```
┌─────────────────────────────────────────────────────────┐
│ HTTP REST API │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 管理后台API │ │ 用户端API │ │ RPC API │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────┐
│ 权限验证层 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ TenantPermissionInterceptor │ │
│ │ (租户权限验证 + 星球权限验证) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────┐
│ Service 业务层 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ TenantSearchService │ │
│ │ (租户索引隔离 + 星球字段过滤) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────┐
│ SearchEngine 适配层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ ZincSearch │ │Elasticsearch│ │ OpenSearch │ ... │
│ │ Adapter │ │ Adapter │ │ Adapter │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────┐
│ 搜索引擎实例 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ ZincSearch │ │Elasticsearch│ │ OpenSearch │ ... │
│ │ Server │ │ Cluster │ │ Cluster │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
多租户索引隔离示例:
┌─────────────────────────────────────────────────────────┐
│ 租户索引隔离 │
│ 租户001: tenant_001_blog, tenant_001_user │
│ 租户002: tenant_002_blog, tenant_002_user │
│ 租户003: tenant_003_blog, tenant_003_user │
└─────────────────────────────────────────────────────────┘
星球字段过滤示例:
┌─────────────────────────────────────────────────────────┐
│ 星球字段过滤 │
│ tenant_001_blog: │
│ - 文档1: {title: "标题1", planet_id: 1001} │
│ - 文档2: {title: "标题2", planet_id: 1002} │
│ - 文档3: {title: "标题3", planet_id: 1001} │
│ 查询星球1001: 返回文档1、文档3 │
│ 查询星球1002: 返回文档2 │
└─────────────────────────────────────────────────────────┘
```
## 🚀 快速开始
### 1. 环境要求
- JDK 17+
- Spring Boot 3.5.4+
- Maven 3.6+
- ZincSearch 0.4.9+ (默认搜索引擎)
### 2. 启动 ZincSearch
使用 Docker 快速启动 ZincSearch:
```bash
docker run -d \
--name zincsearch \
-p 4080:4080 \
-e ZINC_FIRST_ADMIN_USER=admin \
-e ZINC_FIRST_ADMIN_PASSWORD=admin \
-v zinc_data:/data \
public.ecr.aws/zinclabs/zincsearch:latest
```
### 3. 配置搜索模块
在 `application.yml` 中添加搜索配置:
```yaml
yudao:
search:
enabled: true
type: zincsearch
zincsearch:
url: http://localhost:4080
username: admin
password: admin
connect-timeout: 5s
read-timeout: 30s
```
### 4. 添加依赖
在需要使用搜索功能的模块中添加依赖:
```xml
cn.iocoder.cloud
yudao-module-search-api
${revision}
```
### 5. 使用搜索服务
#### 5.1 通过 RPC 调用
```java
@Resource
private SearchApi searchApi;
// 创建索引(租户隔离)
SearchIndexReqDTO indexReq = new SearchIndexReqDTO();
indexReq.setTenantId(1L); // 租户ID
indexReq.setIndexName("user_index");
indexReq.setMapping(Map.of("properties", Map.of(
"name", Map.of("type", "text"),
"age", Map.of("type", "integer"),
"planet_id", Map.of("type", "long") // 星球ID字段
)));
CommonResult result = searchApi.createIndex(indexReq);
// 创建文档(租户+星球隔离)
SearchDocumentReqDTO docReq = new SearchDocumentReqDTO();
docReq.setTenantId(1L); // 租户ID
docReq.setPlanetId(1001L); // 星球ID
docReq.setIndexName("user_index");
docReq.setDocument(Map.of("name", "张三", "age", 25));
CommonResult docResult = searchApi.createDocument(docReq);
// 搜索查询(支持多星球查询)
SearchQueryReqDTO queryReq = new SearchQueryReqDTO();
queryReq.setTenantId(1L); // 租户ID
queryReq.setPlanetIds(List.of(1001L, 1002L)); // 星球ID列表
queryReq.setIndexName("user_index");
queryReq.setQuery("张三");
queryReq.setPageNo(1);
queryReq.setPageSize(10);
CommonResult searchResult = searchApi.search(queryReq);
```
#### 5.2 通过 HTTP API 调用
```bash
# 创建索引(租户隔离)
curl -X POST "http://localhost:8080/admin-api/search/index/create" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"tenantId": 1,
"indexName": "user_index",
"mapping": {
"properties": {
"name": {"type": "text"},
"age": {"type": "integer"},
"planet_id": {"type": "long"}
}
}
}'
# 创建文档(租户+星球隔离)
curl -X POST "http://localhost:8080/admin-api/search/document/create" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"tenantId": 1,
"planetId": 1001,
"indexName": "user_index",
"document": {
"name": "张三",
"age": 25
}
}'
# 搜索查询(支持多星球查询)
curl -X POST "http://localhost:8080/admin-api/search/query" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"tenantId": 1,
"planetIds": [1001, 1002],
"indexName": "user_index",
"query": "张三",
"pageNo": 1,
"pageSize": 10
}'
# 获取租户索引列表
curl -X GET "http://localhost:8080/admin-api/search/index/list?tenantId=1" \
-H "Authorization: Bearer YOUR_TOKEN"
# 删除租户索引
curl -X DELETE "http://localhost:8080/admin-api/search/index/delete?tenantId=1&indexName=user_index" \
-H "Authorization: Bearer YOUR_TOKEN"
```
## 📋 API 接口
### 索引管理
| 接口 | 方法 | 路径 | 描述 |
|------|------|------|------|
| 创建索引 | POST | `/search/index/create` | 创建新的搜索索引 |
| 删除索引 | DELETE | `/search/index/delete` | 删除指定索引 |
| 检查索引 | GET | `/search/index/exists` | 检查索引是否存在 |
| 获取索引 | GET | `/search/index/get` | 获取索引详细信息 |
| 索引列表 | GET | `/search/index/list` | 获取所有索引列表 |
### 文档管理
| 接口 | 方法 | 路径 | 描述 |
|------|------|------|------|
| 创建文档 | POST | `/search/document/create` | 创建新文档 |
| 更新文档 | PUT | `/search/document/update` | 更新现有文档 |
| 删除文档 | DELETE | `/search/document/delete` | 删除指定文档 |
| 获取文档 | GET | `/search/document/get` | 获取文档详情 |
| 批量操作 | POST | `/search/document/bulk` | 批量操作文档 |
### 搜索查询
| 接口 | 方法 | 路径 | 描述 |
|------|------|------|------|
| 搜索查询 | POST | `/search/query` | 执行搜索查询 |
## 🏢 多租户和星球隔离
### 多租户索引隔离
本搜索模块实现了严格的多租户索引物理隔离机制:
#### 索引命名策略
- **格式**: `tenant_{tenantId}_{originalIndexName}`
- **示例**:
- 租户001的blog索引: `tenant_001_blog`
- 租户002的user索引: `tenant_002_user`
#### 隔离优势
- **物理隔离**: 每个租户的数据存储在完全独立的索引中
- **安全性**: 租户间数据无法相互访问
- **性能**: 避免了查询时的租户过滤开销
- **扩展性**: 可以为不同租户配置不同的索引策略
### 星球字段过滤
在同一租户内部,通过 `planet_id` 字段实现星球级别的数据过滤:
#### 过滤机制
- **字段名**: `planet_id` (Long类型)
- **自动添加**: 创建/更新文档时自动添加星球ID字段
- **查询过滤**: 搜索时自动添加星球过滤条件
#### 使用场景
- **权限控制**: 用户只能查看有权限的星球数据
- **数据分组**: 同一租户内按星球组织数据
- **灵活查询**: 支持单星球或多星球查询
### 权限验证
#### 租户权限
- 用户只能访问自己所属租户的数据
- 通过拦截器在请求层面进行验证
- 支持超级管理员跳过租户验证
#### 星球权限
- 用户只能访问有权限的星球数据
- 支持多星球权限配置
- 查询时自动过滤无权限的星球
#### 权限配置示例
```java
// 获取用户权限(需要根据实际业务实现)
Long userTenantId = 1L; // 用户所属租户
Set userPlanetIds = Set.of(1001L, 1002L); // 用户有权限的星球
// 权限验证
boolean hasPermission = TenantPermissionValidator.validateTenantAndPlanetPermissions(
requestTenantId, requestPlanetIds, userTenantId, userPlanetIds);
```
## ⚙️ 配置说明
### 基础配置
```yaml
yudao:
search:
enabled: true # 是否启用搜索功能
type: zincsearch # 搜索引擎类型: zincsearch, elasticsearch, opensearch, meilisearch
```
### ZincSearch 配置
```yaml
yudao:
search:
zincsearch:
url: http://localhost:4080 # ZincSearch 服务地址
username: admin # 用户名
password: admin # 密码
connect-timeout: 5s # 连接超时时间
read-timeout: 30s # 读取超时时间
```
### Elasticsearch 配置
```yaml
yudao:
search:
elasticsearch:
url: http://localhost:9200 # Elasticsearch 服务地址
username: elastic # 用户名 (可选)
password: password # 密码 (可选)
connect-timeout: 5s # 连接超时时间
read-timeout: 30s # 读取超时时间
```
## 📝 使用示例
### 多租户博客系统示例
```java
// 1. 创建租户博客索引
SearchIndexReqDTO blogIndexReq = new SearchIndexReqDTO();
blogIndexReq.setTenantId(1L);
blogIndexReq.setIndexName("blog");
blogIndexReq.setMapping(Map.of("properties", Map.of(
"title", Map.of("type", "text"),
"content", Map.of("type", "text"),
"author", Map.of("type", "keyword"),
"planet_id", Map.of("type", "long"),
"created_time", Map.of("type", "date")
)));
searchApi.createIndex(blogIndexReq);
// 2. 创建博客文档(星球1001)
SearchDocumentReqDTO blogDoc1 = new SearchDocumentReqDTO();
blogDoc1.setTenantId(1L);
blogDoc1.setPlanetId(1001L);
blogDoc1.setIndexName("blog");
blogDoc1.setDocument(Map.of(
"title", "Java开发最佳实践",
"content", "本文介绍Java开发的最佳实践...",
"author", "张三",
"created_time", "2024-01-01T10:00:00Z"
));
searchApi.createDocument(blogDoc1);
// 3. 创建博客文档(星球1002)
SearchDocumentReqDTO blogDoc2 = new SearchDocumentReqDTO();
blogDoc2.setTenantId(1L);
blogDoc2.setPlanetId(1002L);
blogDoc2.setIndexName("blog");
blogDoc2.setDocument(Map.of(
"title", "Spring Boot进阶指南",
"content", "深入学习Spring Boot框架...",
"author", "李四",
"created_time", "2024-01-02T14:30:00Z"
));
searchApi.createDocument(blogDoc2);
// 4. 搜索特定星球的博客
SearchQueryReqDTO queryReq = new SearchQueryReqDTO();
queryReq.setTenantId(1L);
queryReq.setPlanetIds(List.of(1001L)); // 只查询星球1001
queryReq.setIndexName("blog");
queryReq.setQuery("Java");
queryReq.setPageNo(1);
queryReq.setPageSize(10);
SearchQueryRespDTO result = searchApi.search(queryReq).getData();
// 5. 搜索多个星球的博客
queryReq.setPlanetIds(List.of(1001L, 1002L)); // 查询多个星球
SearchQueryRespDTO multiResult = searchApi.search(queryReq).getData();
```
### 最佳实践
#### 1. 索引设计
```java
// 推荐的索引映射设计
Map mapping = Map.of("properties", Map.of(
// 业务字段
"title", Map.of("type", "text", "analyzer", "ik_max_word"),
"content", Map.of("type", "text", "analyzer", "ik_max_word"),
"tags", Map.of("type", "keyword"),
// 必需的系统字段
"planet_id", Map.of("type", "long"), // 星球ID(必需)
"created_time", Map.of("type", "date"), // 创建时间
"updated_time", Map.of("type", "date"), // 更新时间
"status", Map.of("type", "keyword") // 状态字段
));
```
#### 2. 权限控制
```java
// 在业务层进行权限验证
@PreAuthorize("@tenantPermissionValidator.validateTenantPermission(#reqDTO.tenantId)")
public CommonResult createDocument(SearchDocumentReqDTO reqDTO) {
// 业务逻辑
}
// 获取用户星球权限
Set userPlanetIds = getCurrentUserPlanetIds();
reqDTO.setPlanetIds(filterAuthorizedPlanetIds(reqDTO.getPlanetIds(), userPlanetIds));
```
#### 3. 查询优化
```java
// 使用合适的分页大小
queryReq.setPageSize(20); // 推荐10-50
// 添加排序条件
queryReq.setSorts(List.of(Map.of("created_time", "desc")));
// 使用字段过滤减少网络传输
queryReq.setFields(List.of("title", "author", "created_time"));
```
## 🔧 扩展开发
### 添加新的搜索引擎适配器
1. 实现 `SearchEngine` 接口:
```java
@Component
public class MySearchEngineImpl implements SearchEngine {
@Override
public Boolean createIndex(SearchIndexReqDTO reqDTO) {
// 实现创建索引逻辑
return true;
}
// 实现其他接口方法...
}
```
2. 在 `SearchConfiguration` 中注册适配器:
```java
@Bean
@ConditionalOnProperty(prefix = "yudao.search", name = "type", havingValue = "mysearch")
public SearchEngine mySearchEngine() {
return new MySearchEngineImpl();
}
```
3. 添加配置类:
```java
@Data
public static class MySearchConfig {
private String url = "http://localhost:8080";
private String apiKey;
// 其他配置...
}
```
## 🧪 测试
运行单元测试:
```bash
mvn test
```
运行集成测试:
```bash
mvn verify
```
## 📊 监控指标
搜索模块提供以下监控指标:
- 搜索请求总数
- 搜索请求耗时
- 索引操作成功率
- 文档操作成功率
- 搜索引擎连接状态
## 🤝 贡献指南
1. Fork 本仓库
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 打开 Pull Request
## 📄 许可证
本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。
## 🙏 致谢
- [ZincSearch](https://github.com/zincsearch/zincsearch) - 轻量级搜索引擎
- [Elasticsearch](https://www.elastic.co/) - 分布式搜索引擎
- [Spring Boot](https://spring.io/projects/spring-boot) - Java 应用框架
- [芋道源码](https://www.iocoder.cn/) - 微服务架构
## 📞 联系我们
如有问题或建议,请通过以下方式联系:
- 提交 [Issue](https://github.com/your-repo/issues)
- 发送邮件至:support@iocoder.cn
- 加入微信群:YudaoSourceCode