Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 69 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<img src="https://img.shields.io/badge/gin-1.10-blue"/>
<img src="https://img.shields.io/badge/vue-3.5.13-green"/>
<img src="https://img.shields.io/badge/MCP-integration-yellow"/>
<img src="https://img.shields.io/badge/Qdrant-Multimodality-purple"/>
</div>

## 简介
Expand All @@ -12,43 +13,81 @@

搭建了一个存储图片的Web应用,实现的功能如下:

1. 用户注册登录和修改个人信息
1. 用户注册、登录和修改个人信息
2. 支持用户登录后上传图片,会自动从图片中提取拍摄时间、地点和像素等信息
3. 支持用户自定义标签和AI识图添加标签
4. 支持在浏览器中对图片进行编辑
5. 显示图片缩略图,支持图片轮播
6. 支持按照多种条件搜索,支持AI对话搜索
7. 可以删除和下载图片

### 技术栈

- 前端:Vue(ts) + Element Plus
- 后端:gin(golang) + gorm
- 数据库:MySQL + Redis
- 后端:gin(golang) + gorm + viper + zap
- 数据库:PostgreSQL + Redis + Qdrant
- 鉴权:JWT

## 本地开发指南

### 环境推荐

- **go:1.24+**
- **node:v22 / npm:10**

### clone 本项目

```bash
git clone git@github.com:cagoya/ImageHub.git
```

### 安装 mysql
### 安装 PostgreSQL

> 这里的docker部署都没启用映射卷,如认为有必要请自行添加。

**开发环境**的数据库可以跑在docker容器中,这里使用docker部署mysql
**开发和测试环境**的数据库可以选择使用docker部署

```bash
# 或者指定版本
docker pull mysql:latest
docker run -d --name mysql-server -p 3306:3306 -e MYSQL_ROOT_PASSWORD="123456" mysql
docker pull postgres
docker run -d \
--name postgre-server\
-p 5432:5432\
-e POSTGRES_PASSWORD="123456"\
postgres
```

为了方便查询,可一并拉取PgAdmin,这里的邮箱随意,只起到一个用户名的作用。

```bash
docker pull dpage/pgadmin4
docker run -d \
--name pgadmin4 \
-p 5433:80 \
-e 'PGADMIN_DEFAULT_EMAIL=user@example.com' \
-e 'PGADMIN_DEFAULT_PASSWORD=123456' \
dpage/pgadmin4
```

### 安装Redis

还是使用docker部署,需要启动键空间通知(Key-space Notifications)机制

```bash
docker pull redis
docker run -d --name redis-server -p 6379:6379 redis
docker run -d\
--name redis-server\
-p 6379:6379\
--notify-keyspace-events Ex\
redis
```

### 安装Qdrant

```bash
docker pull qdrant/qdrant
docker run -p 6333:6333 -p 6334:6334 \
qdrant/qdrant
```

### 启动后端
Expand All @@ -73,11 +112,18 @@ npm install
npm run dev
```

### 启动mcp server

```bash
cd ./mcp
go run main.go
```

## Docker 部署指南

### 使用 Docker Compose 一键部署
### 使用 Docker Compose

项目已配置好 Docker Compose,可以一键启动所有服务(MySQL、Redis、后端、前端)。
项目已配置好 Docker Compose,可以一键启动所有服务

```bash
# 在项目根目录执行
Expand All @@ -91,39 +137,26 @@ docker-compose logs -f

# 停止所有服务
docker-compose down

# 停止并删除数据卷(注意:会删除数据库数据)
docker-compose down -v
```

### 服务访问地址
### 服务访问

- **前端**: http://localhost:5173
对于部署机,可以使用下述地址访问各项服务:

- **前端**: http://localhost:80
- **后端 API**: http://localhost:8888/api
- **MySQL**: localhost:3306
- **PostgreSQL**: localhost:5432
- **Redis**: localhost:6379
- **Qdrant(gRpc)**:localhost:6334

### 单独构建镜像

如果需要单独构建前后端镜像:

```bash
# 构建后端镜像
cd server
docker build -t imagehub-backend:latest .

# 构建前端镜像
cd web
docker build -t imagehub-frontend:latest .
```
对于其它设备(其它PC或移动设备),需要将localhost换成部署机的ip地址,并且只能访问前端(使用了`Nginx`代理)

### 环境变量配置
### 配置

后端服务支持通过环境变量 `CONFIG_FILE` 指定配置文件,默认为 `server.yaml`。Docker 环境中会自动使用 `docker.yaml` 配置,该配置已设置好容器间的服务名称(mysql、redis)。
后端服务支持通过环境变量 `CONFIG_FILE` 指定配置文件:

### 数据持久化
- 开发环境为 `server.yaml`
- 测试环境为 `test.yaml`
- 生产环境(docker)为 `docker.yaml`

Docker Compose 已配置数据卷持久化:
- MySQL 数据存储在 `mysql_data` 卷中
- Redis 数据存储在 `redis_data` 卷中
- 后端上传的图片和日志通过挂载目录持久化
前端在开发和测试环境会硬编码后端服务的URL,在生产环境(docker)使用`Nginx`代理,故不需要担心部署机ip的变化
54 changes: 35 additions & 19 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ services:
- "5432:5432"
networks:
- imagehub-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5

# Redis 服务
redis:
Expand All @@ -31,11 +26,35 @@ services:
- "6379:6379"
networks:
- imagehub-network
healthcheck:
test: ["CMD", "redis-cli", "-a", "123456", "ping"]
interval: 10s
timeout: 3s
retries: 5

# Qdrant 向量数据库服务
qdrant:
image: qdrant/qdrant:latest
container_name: imagehub-qdrant
restart: unless-stopped
ports:
- "6333:6333" # HTTP API
- "6334:6334" # gRPC API
networks:
- imagehub-network

# MCP 服务
mcp:
build:
context: ./mcp
dockerfile: Dockerfile
container_name: imagehub-mcp
restart: unless-stopped
depends_on:
- qdrant
ports:
- "7777:7777"
environment:
- TZ=Asia/Shanghai
- QDRANT_HOST=qdrant
- QDRANT_PORT=6334
networks:
- imagehub-network

# 后端服务
backend:
Expand All @@ -45,10 +64,9 @@ services:
container_name: imagehub-backend
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
- postgres
- redis
- qdrant
ports:
- "8888:8888"
volumes:
Expand All @@ -60,20 +78,18 @@ services:
networks:
- imagehub-network

# 前端服务
# 前端服务 (Nginx)
frontend:
build:
context: ./web
dockerfile: Dockerfile
# 生产环境推荐:不设置 args,使用相对路径 /api(通过 Nginx 代理到后端)
container_name: imagehub-frontend
restart: unless-stopped
depends_on:
- backend
ports:
- "5173:5173"
volumes:
- ./web:/app
- /app/node_modules
- "80:80"
networks:
- imagehub-network

Expand Down
38 changes: 38 additions & 0 deletions mcp/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 构建阶段
FROM golang:1.24-alpine AS builder

# 设置工作目录
WORKDIR /app

# 安装必要的构建工具
RUN apk add --no-cache git

# 复制 go mod 文件
COPY go.mod go.sum ./

# 下载依赖
RUN go mod download

# 复制源代码
COPY . .

# 构建应用
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o mcp-server main.go

# 运行阶段
FROM alpine:latest

# 安装必要的运行时依赖
RUN apk --no-cache add ca-certificates tzdata

WORKDIR /app

# 从构建阶段复制二进制文件
COPY --from=builder /app/mcp-server .

# 暴露端口
EXPOSE 7777

# 运行应用
CMD ["./mcp-server"]

18 changes: 16 additions & 2 deletions mcp/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"log"
"net/http"
"os"
"strconv"

"mcp_server/utils"

Expand Down Expand Up @@ -40,9 +42,21 @@ func QdrantSearch(ctx context.Context, _ *mcp.CallToolRequest, in QdrantSearchPa
}

// 3. 连接 Qdrant
// 从环境变量获取 Qdrant 配置,默认为 localhost:6334
qdrantHost := os.Getenv("QDRANT_HOST")
if qdrantHost == "" {
qdrantHost = "localhost"
}
qdrantPort := 6334
if portEnv := os.Getenv("QDRANT_PORT"); portEnv != "" {
if port, err := strconv.Atoi(portEnv); err == nil {
qdrantPort = port
}
}

qdrantClient, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
Host: qdrantHost,
Port: qdrantPort,
})
if err != nil {
return nil, QdrantSearchResult{}, fmt.Errorf("连接 Qdrant 失败: %v", err)
Expand Down
13 changes: 12 additions & 1 deletion server/config/docker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ redis:
port: 6379
password: "123456"
db: 0
qdrant:
host: qdrant
port: 6334
zap:
prefix: image_hub
timeFormat: 2006/01/02 - 15:04:05.00000
Expand All @@ -24,4 +27,12 @@ zap:
compress: true
output:
- "./log/image_hub.log"

gaode:
apiKey: "eaf59456283e912bb394b3f4970027e1"
baidu:
apiKey: "siOl6J2rzuM4CIgD3ZuDpVKY"
secretKey: "YyFRinKgfSMOKR8MtowoA0os8gxPpphv"
ali:
apikey: "sk-9ef56cbc5d944b65b1bb612fd7864877"
deepseek:
apiKey: "sk-884a6ecd756644cdb4ead4240b0b73cd"
11 changes: 10 additions & 1 deletion server/config/server.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,13 @@ zap:
backups: 10
compress: true
output:
- "./log/image_hub.log"
- "./log/image_hub.log"
gaode:
apiKey: "eaf59456283e912bb394b3f4970027e1"
baidu:
apiKey: "siOl6J2rzuM4CIgD3ZuDpVKY"
secretKey: "YyFRinKgfSMOKR8MtowoA0os8gxPpphv"
ali:
apikey: "sk-9ef56cbc5d944b65b1bb612fd7864877"
deepseek:
apiKey: "sk-884a6ecd756644cdb4ead4240b0b73cd"
4 changes: 3 additions & 1 deletion server/config/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ redis:
db: 1
qdrant:
host: localhost
port: 6334
port: 6334
gaode:
apiKey: "eaf59456283e912bb394b3f4970027e1"
Loading
Loading