Skip to content

dongjiang1989/opensearch-api

Repository files navigation

OpenSearch File API

多租户文件索引服务,基于 OpenSearch 实现图片、PDF、文档等文件的索引、存储和搜索功能。

功能特性

  • 多租户支持: 基于 JWT 认证和租户隔离的索引管理
  • 文件索引: 支持 PDF、图片、Office 文档、文本文件等格式
  • 全文搜索: 基于 OpenSearch 的高效全文检索
  • 灵活存储: 支持本地存储和 S3 兼容存储(MinIO、AWS S3)
  • 容器化部署: 提供 Docker、Docker Compose 和 Helm Chart
  • 监控指标: Prometheus 格式的丰富监控指标

快速开始

使用 Docker Compose

# 启动服务(OpenSearch + MinIO + API)
docker compose -f deployments/docker/docker-compose.yml up -d

# 查看日志
docker compose -f deployments/docker/docker-compose.yml logs -f opensearch-file-api

# 停止服务
docker compose -f deployments/docker/docker-compose.yml down

注意:服务端口映射已调整为 18080:8080,API 地址为 http://localhost:18080

本地开发

# 安装依赖
go mod download

# 运行服务
make run

# 或构建后运行
make build
./bin/opensearch-file-api

API 文档

认证

获取 JWT Token:

curl -X POST http://localhost:18080/api/v1/token \
  -H "Content-Type: application/json" \
  -d '{
    "tenant_id": "tenant-1",
    "role": "admin"
  }'

文件上传

curl -X POST http://localhost:18080/api/v1/files \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-ID: tenant-1" \
  -F "file=@document.pdf" \
  -F "description=示例文档" \
  -F "tags[]=重要" \
  -F "tags[]=合同"

文件搜索

# GET 方式搜索
curl -X GET "http://localhost:18080/api/v1/search?q=合同&file_type=pdf" \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-ID: tenant-1"

# POST 方式高级搜索
curl -X POST http://localhost:18080/api/v1/search \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-ID: tenant-1" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "合同",
    "filters": {
      "file_type": "pdf"
    },
    "size": 20,
    "from": 0
  }'

向量检索(自动 Embedding)

上传文件或传入文本,系统自动通过 Embedding 服务转换为向量后进行相似度检索:

# JSON 模式:传入文本 query
curl -X POST http://localhost:18080/api/v1/search/retrieve \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-ID: tenant-1" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "machine learning algorithms",
    "k": 10,
    "field": "content_vector"
  }'

# Multipart 模式:上传文件
curl -X POST http://localhost:18080/api/v1/search/retrieve \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-ID: tenant-1" \
  -F "file=@document.pdf" \
  -F "query=补充关键词" \
  -F "k=10"
参数 类型 必填 说明
query string JSON 模式必填 文本查询关键词
file file Multipart 模式必填 上传文件,自动提取内容并转为向量
k int 返回结果数量,默认 10,最大 100
field string 向量字段名,默认 content_vector
filters object 过滤条件

注意:需要配置嵌入服务(embedding.provider=openai/local/clip),否则返回 503。

KNN 向量搜索

通过向量进行相似度搜索,适用于语义搜索、图片相似度检索等场景:

curl -X POST http://localhost:18080/api/v1/search/knn \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-ID: tenant-1" \
  -H "Content-Type: application/json" \
  -d '{
    "vector": [0.1, 0.2, 0.3, ...],
    "field": "content_vector",
    "k": 10,
    "filters": {
      "file_type": "pdf"
    }
  }'
参数 类型 必填 说明
vector array 查询向量
field string 向量字段名,默认 content_vector
k int 返回结果数量,默认 10,最大 100
filters object 过滤条件

混合搜索(文本 + 向量)

结合全文搜索和向量搜索,获得更精准的搜索结果:

curl -X POST http://localhost:18080/api/v1/search/hybrid \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-ID: tenant-1" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "合同条款",
    "vector": [0.1, 0.2, 0.3, ...],
    "vector_field": "content_vector",
    "k": 10,
    "filters": {}
  }'
参数 类型 必填 说明
query string 文本查询关键词
vector array 查询向量
vector_field string 向量字段名,默认 content_vector
k int 返回结果数量,默认 10,最大 100
filters object 过滤条件

文件列表

curl -X GET "http://localhost:18080/api/v1/files?page=1&size=20" \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-ID: tenant-1"

删除文件

curl -X DELETE http://localhost:18080/api/v1/files/<file_id> \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-ID: tenant-1"

健康检查

# 健康检查(检查 OpenSearch 连接)
curl http://localhost:18080/health

# Ping 检查(轻量级)
curl http://localhost:18080/ping

监控指标

# 获取 Prometheus 格式指标
curl http://localhost:18080/metrics

可用的指标包括:

  • opensearch_api_http_requests_total - HTTP 请求总数
  • opensearch_api_http_request_duration_seconds - 请求延迟(秒)
  • opensearch_api_http_request_size_bytes - 请求体大小(字节)
  • opensearch_api_http_response_size_bytes - 响应体大小(字节)
  • opensearch_api_http_inflight_requests - 正在处理的请求数
  • go_* - Go 运行时指标(goroutines、GC 等)
  • process_* - 进程指标(CPU、内存等)

租户管理

# 创建租户
curl -X POST http://localhost:18080/api/v1/admin/tenants \
  -H "Content-Type: application/json" \
  -d '{
    "id": "tenant-1",
    "name": "测试租户",
    "description": "用于测试的租户"
  }'

# 获取租户信息
curl -X GET http://localhost:18080/api/v1/admin/tenants/tenant-1

# 列出租户
curl -X GET http://localhost:18080/api/v1/admin/tenants

# 更新租户
curl -X PUT http://localhost:18080/api/v1/admin/tenants/tenant-1 \
  -H "Content-Type: application/json" \
  -d '{
    "name": "新租户名称",
    "description": "更新后的描述"
  }'

# 删除租户(软删除,标记为已删除但保留数据)
curl -X DELETE http://localhost:18080/api/v1/admin/tenants/tenant-1

# 彻底删除租户(不可恢复)
curl -X DELETE http://localhost:18080/api/v1/admin/tenants/tenant-1/hard

多租户搜索

支持通过 X-Tenant-ID 传入多个租户(逗号分隔),实现跨租户联合搜索。

# 跨租户全文搜索
curl -X POST http://localhost:18080/api/v1/search \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-ID: tenant-a,tenant-b" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "合同",
    "size": 20
  }'

# 跨租户 KNN 向量搜索
curl -X POST http://localhost:18080/api/v1/search/knn \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-ID: tenant-a,tenant-b" \
  -H "Content-Type: application/json" \
  -d '{
    "vector": [0.1, 0.2, 0.3, ...],
    "k": 10
  }'

# 跨租户混合搜索
curl -X POST http://localhost:18080/api/v1/search/hybrid \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-ID: tenant-a,tenant-b" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "合同条款",
    "vector": [0.1, 0.2, 0.3, ...],
    "k": 10
  }'

# 跨租户聚合统计(含每租户细分)
curl -X POST http://localhost:18080/api/v1/search/aggregate \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-ID: tenant-a,tenant-b" \
  -H "Content-Type: application/json" \
  -d '{"field": "file_type"}'

# 跨租户文件计数
curl -X GET http://localhost:18080/api/v1/search/count \
  -H "Authorization: Bearer <token>" \
  -H "X-Tenant-ID: tenant-a,tenant-b"

跨索引 Score 归一化(D11)

多租户搜索使用 OpenSearch dfs_query_then_fetch 搜索类型,在所有分片上收集词频信息后再评分,消除各租户独立索引带来的 IDF 偏差。搜索结果中每个 hit 携带 index 字段标识来源索引。

聚合租户细分(D12)

聚合查询返回两个维度的结果:

  • buckets:合并后的总体聚合结果(向后兼容)
  • by_tenant:每租户维度的细分数据(tenantID -> field_value -> count

配置

配置文件

详见 configs/config.yaml,支持以下配置项:

配置项 说明 默认值
server.port 服务端口 8080
server.host 监听地址 0.0.0.0
server.mode 运行模式 release (debug/release/test)
server.read_timeout 读超时 30s
server.write_timeout 写超时 60s
opensearch.host OpenSearch 主机 localhost
opensearch.port OpenSearch 端口 9200
opensearch.username OpenSearch 用户名 admin
opensearch.password OpenSearch 密码 admin
opensearch.secure 是否使用 HTTPS true
opensearch.index_prefix 租户索引前缀 tenant
storage.type 存储类型 local (local/s3)
storage.local_path 本地存储路径 ./data/files
storage.doc_parse_provider 文档解析提供者(必须配置) qwen
storage.doc_parse_api_url DashScope API 地址 -
storage.doc_parse_api_key DashScope API 密钥 -
storage.doc_parse_model 文档解析模型(PDF/Office/EPUB) qwen-long
storage.doc_parse_vl_model 视觉解析模型(Image) qwen3-vl-plus
storage.doc_parse_vl_api_url 视觉模型 API 地址(可选,默认同 doc_parse_api_url) -
storage.doc_parse_vl_api_key 视觉模型 API Key(可选,默认同 doc_parse_api_key) -
jwt.secret JWT 密钥 change-this-secret-key
jwt.issuer JWT 签发者 opensearch-file-api
jwt.expire_time Token 过期时间 24h
log.level 日志级别 info
log.format 日志格式 json (json/console)
embedding.provider 嵌入服务提供者 openai (openai/local/clip/none)
embedding.model 嵌入模型 text-embedding-3-small
embedding.dimensions 向量维度 1536
embedding.api_url 嵌入服务 API 地址 -
embedding.api_key 嵌入服务 API 密钥 -
embedding.timeout 请求超时(秒) 30

启用向量检索

向量检索(/search/retrieve)需要配置嵌入服务。支持的 provider:

Provider 说明 适用场景
openai OpenAI 兼容 API OpenAI、DashScope text-embedding-v4、Ollama v1 API 等兼容服务
local 本地 Ollama 服务 使用本地 Ollama 部署模型
clip CLIP 多模态服务 图片+文本多模态嵌入
none 不启用(默认) 仅使用全文搜索,无需向量服务

环境变量示例(使用 DashScope text-embedding-v4):

export OPENSEARCH_EMBEDDING_PROVIDER=openai
export OPENSEARCH_EMBEDDING_APIURL=https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings
export OPENSEARCH_EMBEDDING_APIKEY=sk-your-dashscope-api-key
export OPENSEARCH_EMBEDDING_MODEL=text-embedding-v4
export OPENSEARCH_EMBEDDING_DIMENSIONS=1536
export OPENSEARCH_EMBEDDING_TIMEOUT=120

环境变量示例(使用 Ollama 本地部署):

export OPENSEARCH_EMBEDDING_PROVIDER=openai
export OPENSEARCH_EMBEDDING_APIURL=http://localhost:11434/v1/embeddings
export OPENSEARCH_EMBEDDING_MODEL=nomic-embed-text
export OPENSEARCH_EMBEDDING_TIMEOUT=60

Docker Compose 示例:

environment:
  - OPENSEARCH_EMBEDDING_PROVIDER=openai
  - OPENSEARCH_EMBEDDING_APIURL=http://ollama:11434/v1/embeddings
  - OPENSEARCH_EMBEDDING_MODEL=nomic-embed-text
  - OPENSEARCH_EMBEDDING_TIMEOUT=60

Kubernetes 部署

# 安装 Helm Chart
helm install opensearch-file-api ./deployments/helm/opensearch-file-api \
  --values values.yaml

# 自定义配置
helm install opensearch-file-api ./deployments/helm/opensearch-file-api \
  --set config.opensearch.host=opensearch.example.com \
  --set config.storage.type=s3 \
  --set config.storage.s3Bucket=my-bucket

开发

运行测试

# 单元测试
make test

# 集成测试(需要 Docker)
make test-integration

# 生成覆盖率报告
make test-coverage

# E2E 端到端测试(需要 Docker Compose 运行中)
bash scripts/test_e2e.sh

# Embedding E2E 测试(需要 mock embedding server,自动启动)
bash scripts/test_e2e_embedding.sh

# Doc Parse E2E 测试(需要 mock doc parse server,自动启动)
bash scripts/test_e2e_doc_parse.sh

E2E 测试覆盖:租户管理 → JWT 生成 → 文件上传 → 文件操作 → 文本搜索 → 租户隔离 → 向量搜索(KNN/混合/自动Embedding检索) → 单租户检索 → 跨租户联合搜索 → 聚合统计(含租户细分)→ 健康检查

Embedding E2E 测试覆盖:启动 mock embedding server → 重启 app(启用 embedding) → 上传文件(自动生成向量) → retrieve JSON 模式 → retrieve multipart 模式 → 文件+query 混合模式

Doc Parse E2E 测试覆盖:启动 mock doc parse server → 重启 app(启用文档解析) → 上传全格式文件(TXT/HTML/MD/CSV/JSON/PDF/DOCX/XLSX/PNG) → 验证文本直接提取 → 验证文档 API 提取 → 验证 mock API 调用 → 聚合统计 → 过滤搜索

代码质量

# 运行 linter
make lint

# 自动修复
make lint-fix

构建 Docker 镜像

make docker-build

支持的文件格式

类型 格式 内容提取
PDF .pdf 文本内容、扫描件 OCR、元数据(qwen-long,fileid://)
图片 .jpg, .png, .gif, .webp, .tiff, .bmp 元数据(尺寸、格式)、OCR 文字提取(qwen3-vl-plus,base64)
文本 .txt, .md, .json, .csv 纯文本直接返回
HTML .html, .htm 提取纯文本(过滤 script/style)
Office .doc, .docx, .xls, .xlsx, .ppt, .pptx 内容与表格提取(qwen-long,fileid://)
RTF .rtf 文本内容(qwen-long,fileid://)
电子书 .epub 章节内容提取(qwen-long,fileid://)

Qwen 双模型文档解析

Qwen 双模型架构根据文件类型自动路由到最合适的模型,必须配置

模型 用途 协议 适用格式
qwen-long(默认) 文档解析 文件上传 + fileid:// PDF、Office、RTF、EPUB
qwen3-vl-plus(默认) 视觉解析 base64 image_url JPG、PNG、GIF、WebP、TIFF、BMP

提取架构:

QwenDocExtractor(doc_parse_provider=qwen,必须配置)
├── PDF/Office/EPUB → 上传到 DashScope /v1/files → fileid:// 引用 → qwen-long
├── Image → base64 data URL → image_url → qwen3-vl-plus
└── Text/HTML/CSV/JSON → 直接读取(不调用 API)

配置方式:

# config.yaml
storage:
  doc_parse_provider: "qwen"
  doc_parse_api_url: "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"
  doc_parse_api_key: "${DASHSCOPE_API_KEY}"  # 需配合环境变量使用
  doc_parse_model: "qwen-long"              # 文档解析模型(PDF/Office/EPUB)
  doc_parse_vl_model: "qwen3-vl-plus"       # 视觉解析模型(Image)
  # doc_parse_vl_api_url: ""                # 可选,默认同 doc_parse_api_url
  # doc_parse_vl_api_key: ""                # 可选,默认同 doc_parse_api_key
# 环境变量(推荐方式,viper BindEnv 优先级高于配置文件)
export OPENSEARCH_STORAGE_DOC_PARSE_PROVIDER=qwen
export OPENSEARCH_STORAGE_DOC_PARSE_API_URL=https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions
export OPENSEARCH_STORAGE_DOC_PARSE_API_KEY=sk-your-dashscope-api-key
export OPENSEARCH_STORAGE_DOC_PARSE_MODEL=qwen-long
export OPENSEARCH_STORAGE_DOC_PARSE_VL_MODEL=qwen3-vl-plus
参数 说明 默认值
doc_parse_provider 文档解析提供者(必须配置为 qwen qwen
doc_parse_api_url DashScope chat completions API 地址 -
doc_parse_api_key DashScope API 密钥 -
doc_parse_model 文档解析模型(PDF/Office/EPUB) qwen-long
doc_parse_vl_model 视觉解析模型(Image) qwen3-vl-plus
doc_parse_vl_api_url 视觉模型 API 地址(可选,默认复用 doc_parse_api_url) -
doc_parse_vl_api_key 视觉模型 API Key(可选,默认复用 doc_parse_api_key) -

注意: 配置文件中 doc_parse_api_key: "${DASHSCOPE_API_KEY}" 是占位符,viper 不会自动展开。 实际生效需通过环境变量 OPENSEARCH_STORAGE_DOC_PARSE_API_KEY 注入,或在 Docker Compose 中设置。

OpenSearch 索引映射

文件索引结构

每个租户的文件存储在独立的索引中,命名格式为 tenant_{tenantID}_files

字段 类型 说明
filename text + keyword 文件名,支持全文搜索和精确匹配
content text 文件内容(从 PDF/文档中提取)
content_type keyword MIME 类型
file_type keyword 文件类型(pdf, image, text 等)
file_size long 文件大小(字节)
description text 文件描述
tags keyword 标签数组
metadata object 元数据(width, height, duration, pages, author, created_at)
storage_path keyword 存储路径
tenant_id keyword 租户 ID
created_at date 创建时间
updated_at date 更新时间
content_vector knn_vector (1536维) 文本嵌入向量,用于语义搜索
image_vector knn_vector (512维) 图片嵌入向量(CLIP),用于图片相似度搜索

向量搜索

OpenSearch 使用 knn_vector 类型存储向量,底层采用 NMSLIB 引擎:

  • content_vector (1536维): 适用于 OpenAI text-embedding-3-small 等模型
  • image_vector (512维): 适用于 CLIP 多模态模型

注意:NMSLIB 引擎不支持 KNN 查询中的过滤器(filters),混合搜索已针对此限制进行优化。

Swagger API 文档

项目使用 Swag 生成 OpenAPI 2.0 规范的文档:

# 生成 Swagger 文档
make swag

# 生成的文件位于 api/swagger.yaml
┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│   Client    │────▶│  File API    │────▶│ OpenSearch  │
│             │     │  (Gin + Go)  │     │   Cluster   │
└─────────────┘     └──────┬───────┘     └─────────────┘
                           │
                    ┌──────▼───────┐
                    │   Storage    │
                    │  (Local/S3)  │
                    └──────────────┘

┌─────────────┐
│  Prometheus │◀─── GET /metrics
│  / Grafana  │     (监控指标)
└─────────────┘

API 接口概览

接口 方法 认证 说明
/health GET 健康检查(检查 OpenSearch 连接)
/ping GET 轻量级 Ping 检查
/metrics GET Prometheus 监控指标
/api/v1/token POST 生成 JWT Token(测试用)
/api/v1/admin/tenants POST/GET 创建/列出租户
/api/v1/admin/tenants/:id GET 获取租户
/api/v1/admin/tenants/:id PUT 更新租户
/api/v1/admin/tenants/:id DELETE 删除租户(软删除)
/api/v1/admin/tenants/:id/hard DELETE 彻底删除租户
/api/v1/files POST/GET 上传文件/列出文件
/api/v1/files/:id GET/DELETE 下载文件/删除文件
/api/v1/files/:id/metadata GET 获取文件元数据
/api/v1/search GET/POST 搜索文件(支持多租户,逗号分隔 X-Tenant-ID
/api/v1/search/aggregate POST 聚合查询(返回 buckets + by_tenant 细分)
/api/v1/search/count GET 统计文件数量(支持多租户)
/api/v1/search/knn POST KNN 向量搜索(支持多租户)
/api/v1/search/hybrid POST 混合搜索(文本 + 向量,支持多租户)
/api/v1/search/retrieve POST 向量检索(自动 Embedding)

License

MIT License

About

opensearch-api apiserver

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors