关于 Cellarion Wine Registry
Cellarion Wine Registry 是面向葡萄酒收藏者的线上酒窖管理服务,也可自行托管。它提供共享葡萄酒注册表、酒窖管理和 AI 品酒师,并内置 MCP 服务供兼容客户端管理酒窖。
主要功能
- 01记录酒瓶、酒架与开瓶情况
- 02葡萄酒搜索与酒标扫描
- 03成熟期提醒和收藏统计
- 04离线使用并在联网后同步
- 05AI 酒窖聊天与分享功能
适用场景
- 01整理多座酒窖中的酒瓶
- 02扫描酒标并补充葡萄酒信息
- 03离线查看酒瓶位置并记录品饮
- 04通过 AI 查询自有收藏
MCP 相关
内置 MCP 服务通过 `/api/mcp` 提供访问,可让 Claude 等 MCP 客户端读取和管理酒窖。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://cellarion.app/api/mcp/publicclaude mcp add --transport http app-cellarion-wine-registry https://cellarion.app/api/mcp/public{"mcpServers":{"app-cellarion-wine-registry":{"type":"http","url":"https://cellarion.app/api/mcp/public"}}}在 Cursor 中打开{"servers":{"app-cellarion-wine-registry":{"type":"http","url":"https://cellarion.app/api/mcp/public"}}}在 VS Code 中打开{"mcpServers":{"app-cellarion-wine-registry":{"type":"http","url":"https://cellarion.app/api/mcp/public"}}}{"mcpServers":{"app-cellarion-wine-registry":{"serverUrl":"https://cellarion.app/api/mcp/public"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
工具
7此工具无需参数。
详细介绍
Cellarion
Cellarion 是一款托管式酒窖应用——可在 cellarion.app 使用的即用型在线服务。 创建免费账户,即可开始追踪酒瓶、将它们整理到酒窖和酒架中、搜索共享葡萄酒登记库、获取侍酒师精选的适饮期建议,并与 AI 侍酒师聊聊你的藏酒。无需安装、无需服务器、无需设置——注册即可使用。所有功能永久免费。
只想使用 Cellarion? 前往 cellarion.app 注册即可。你不需要克隆此代码库、运行 Docker 或自行托管任何内容。此外还有一个公开的**演示账户**(在首页点击“试用演示”),以及一款 Android 应用,可在 Google Play 下载。
Cellarion 同时也是开源软件(AGPL-3.0),因此如果你更愿意运行自己的私有实例,也可以自行托管。本文其余部分介绍自托管方式——请参阅快速开始。大多数人直接使用 cellarion.app 上的托管服务即可。
托管服务(推荐)
Cellarion 已上线并向公众开放:
这是使用 Cellarion 的主要方式。创建账户,即可立即开始使用完整服务——所有功能永久免费。 无需信用卡、没有试用期限、没有付费墙功能,也无需安装或维护任何内容。如果你愿意支持我们,可以选择支持者、赞助人和捐助者等级(按月或按年),也可以通过 GitHub Sponsors 资助开发——这些支持不会解锁任何额外内容,只代表我们对你的感谢。
功能
你的酒窖
- 酒瓶追踪 — 记录每瓶酒的年份、生产商、产区、价格、评分和品鉴笔记;只需单击一次,即可为已有藏酒添加更多同款酒
- 酒窖和酒架管理 — 支持多个酒窖、自定义酒架网格(最大 20×20),以及用于标记实际摆放位置的 3D 酒窖视图
- 已开瓶追踪 — 开瓶后可在数日内倒取酒杯,之后关闭或喝完酒瓶;系统还会根据保存情况提醒你尽快饮用
- 离线模式 — 在设备上保存酒窖、酒架和酒瓶的副本,让你即使在没有信号的酒窖里也能使用应用:浏览、查找酒架上的酒瓶、饮用/开瓶/倒酒、摆放和移动酒瓶,以及编辑笔记和评分。恢复联网后,变更会同步——每项变更只应用一次(幂等键),绝不会在未提示的情况下覆盖他人的变更(冲突会列入需要处理)。此功能默认关闭,须由用户选择开启:已安装的应用只会询问一次,也可以随时在设置中切换
- 预留酒瓶 — 将酒瓶标记为“已预留”(用于生日、晚宴等),让推荐和饮用操作遵循预留状态
- 适饮期提醒 — 每种葡萄酒和年份都有侍酒师精选的成熟期窗口;当酒瓶即将达到巅峰、处于适饮期或已错过适饮期时发出提醒
- 丰富的统计数据 — 提供图表、世界分级设色地图,以及按国家、葡萄品种、价值和适饮状态分类的统计
- 导入和导出 — 可从 Vivino(包括饮用历史)、CellarTracker、Ploc、通用 CSV 或 Cellarion 自有 JSON 格式导入藏酒;也可将所有内容导出为 JSON,或导出包含图片的 ZIP 文件
共享登记库
- 智能搜索 — 由 Meilisearch 提供支持的模糊搜索,具有强力去重和规范键匹配功能
- 酒标扫描 — 拍摄葡萄酒酒标照片,让 AI 填写详细信息
- 地区葡萄品种名称 — 每种葡萄只保留一个规范品种名称,并按照酒标上的写法显示(Douro 波特酒标注为 Tinta Roriz,Alentejo 红葡萄酒标注为 Aragonez——两者在数据中都存储为 Tempranillo)
- 登记库质量工具 — 通过重复项/碎片化队列、跨字段领域检查、名称检查以及侍酒师更正建议工作流,保持共享数据整洁 AI
- AI 酒窖聊天 — 询问有关藏酒的问题,例如食物搭配、场合选酒和酒窖状况(Claude + Voyage 嵌入,通过 MongoDB 搜索;只会根据你实际拥有的葡萄酒作答)
- 连接你自己的 AI — 内置的 MCP 服务 可让 Claude 及任何支持 MCP 的客户端通过对话读取和管理你的酒窖
- 自带模型 — 自托管用户可以将所有 AI 功能指向任意兼容 OpenAI 的端点(Ollama、vLLM、LM Studio),而不必使用 Anthropic/Voyage
社区与分享
- 共享酒窖 — 邀请他人浏览或共同管理酒窖,并按角色分配访问权限
- 葡萄酒清单、评价与讨论 — 创建可分享的清单、评价葡萄酒、参与讨论、关注其他用户并接收补货提醒
- 品酒日志与愿望清单 — 记录私人笔记,并维护想尝试的葡萄酒清单
平台
- 气候监测 — 连接酒窖温度/湿度传感器(兼容 Home Assistant 的数据接入 API),并为每个酒窖提供仪表板
- 单点登录 — 除邮箱/密码登录外,还支持可选的 Google SSO 或你自己的 OIDC 提供方(Pocket ID、Authentik、Keycloak、Zitadel、Authelia)
- 可安装应用 — 支持推送通知和离线使用的 PWA(Service Worker 会保留每个构建版本以供离线使用),另提供 Android 应用,可从 Google Play 获取
- 国际化 — 由社区通过 Weblate 翻译(帮助翻译)
- 隐私与 GDPR — 完整的数据导出、带冷静期的账户删除、一键退订邮件,以及可选的自托管无 Cookie 分析工具(Umami)
- 全部免费 — 可选的支持者/赞助者/慈善家等级(Stripe,按月或按年付费)以及 GitHub Sponsors 为开发提供资金支持;这些等级不会解锁任何额外功能
- 侍酒师与管理工具 — 成熟度/定价管理界面、葡萄酒申请、质量报告、注册表健康监控、审计日志和超级管理员仪表板
技术栈
- MongoDB 7 — 数据库(Mongoose 8)
- Express 4 — 后端 API
- React 19 — 前端(React Router 6,使用 Vite 7 构建)
- Node.js 24 — 运行时
- Meilisearch — 模糊搜索引擎
- Voyage AI — 葡萄酒嵌入生成(可替换为任意兼容 OpenAI 的端点);向量存储在 MongoDB 中,由后端进行比对——无需向量数据库
- Anthropic Claude — 标签扫描 + AI 聊天(可替换,机制相同)
- MCP — 面向 AI 助手的 Model Context Protocol 服务(
/api/mcp),支持 OAuth - Stripe — 可选的支持者付款(托管式 Checkout + Portal)
- nginx — 提供 React SPA 服务,并将
/api/代理到后端(内部) - Traefik — 外部反向代理(需自行提供;此 Compose 文件不包含)
- Docker Compose — 容器化
- rembg — Python/Flask 背景移除微服务
- Umami — 可选的自托管无 Cookie 分析工具(compose
--profile analytics)
自托管(快速开始)
本节仅适用于想要运行自己私人实例的用户。如果你只想使用 Cellarion,请前往 cellarion.app,无需任何设置。
前置条件
- Docker + Docker Compose
运行应用
应用通过 Traefik 路由,因此请在首次执行 up 之前创建外部 web Docker 网络(Compose 将其声明为外部网络,否则第一条命令会失败):
git clone https://github.com/jagduvi1/Cellarion.git
cd Cellarion
cp .env.example .env
# Edit .env and set JWT_SECRET and MEILI_MASTER_KEY to strong random strings
docker network create web # once; skip if it already exists
docker-compose up --build
| URL | 说明 |
|---|---|
| http://localhost | 前端(React SPA)— 通过 Traefik 提供服务 |
| http://localhost/api/health | 后端健康检查 |
将自己设为管理员
全新安装没有管理员。先在应用中注册自己的账户,然后运行:
docker exec cellarion-backend node src/scripts/make-admin.js <your username or email>
退出后重新登录即可查看管理页面。--remove 会再次移除该角色。
填充演示数据
容器运行后:
docker exec cellarion-backend node src/seed-demo.js
这会创建演示账户,以及初始分类体系、葡萄酒登记条目和包含样品酒瓶的演示酒窖:
| 账户 | 邮箱 | 密码 | 角色 |
|---|---|---|---|
| 管理员 | [email protected] | Admin1234!demo | admin |
| 演示用户 | [email protected] | User1234!demo | user |
这些是本地开发凭据。部署到任何公开环境之前,请先更改它们。
停止
docker-compose down # keep data
docker-compose down -v # also remove all volumes (wipes database)
升级
git pull
docker-compose up -d --build
从 v1.241.0 或更早版本升级:Qdrant 已停用。 AI 酒窖聊天、补货建议和 MCP 相似度工具背后的葡萄酒向量现在存储在 MongoDB 中,并由后端自行比较——不再需要运行向量数据库。升级后,旧的 qdrant 容器会作为孤立容器继续运行,因此可以一次性将其中的向量复制过来,无需重新嵌入,也不会产生嵌入费用:
# dry run (counts only), then the real copy
docker-compose exec -e QDRANT_URL=http://qdrant:6333 backend node src/scripts/migrate-vectors-from-qdrant.js
docker-compose exec -e QDRANT_URL=http://qdrant:6333 backend node src/scripts/migrate-vectors-from-qdrant.js --apply
# then remove the old container, and later its volume
docker-compose up -d --remove-orphans
docker volume ls | grep qdrant # e.g. cellarion_qdrant-data → docker volume rm cellarion_qdrant-data
- 如果你设置过
QDRANT_API_KEY,请在两条命令中都添加-e QDRANT_API_KEY=<your key>(新的 compose 不再传递该变量)。 - 你也可以不复制,而是在 SuperAdmin → AI 中运行完整嵌入任务,通过你的嵌入提供商重新嵌入每一款葡萄酒。
- 如果从未配置嵌入(没有
VOYAGE_API_KEY/EMBEDDING_PROVIDER),则无需复制任何内容——只需移除旧容器。 - 之后可以回退到 v1.241.0,但回退后请运行一次完整嵌入任务,并在再次升级后再运行一次。
架构
以下目录树展示了代码库的整体结构——这里只列出重点,并非所有文件。后端包含 57 个 Mongoose 模式和 约 65 个路由模块(44 个顶层模块 + admin/ + somm/),以及 MCP 服务。
Cellarion/
├── backend/
│ ├── server.js # Entry point
│ └── src/
│ ├── app.js # Express app setup, mounting, rate limiters
│ ├── config/ # db, plans, upload, aiConfig (models, prompts, limits)
│ ├── middleware/
│ │ ├── auth.js # JWT + role middleware (requireAuth, requireAdmin, requireSomm)
│ │ └── bottleAccess.js # requireBottleAccess(minRole) factory
│ ├── models/ # 57 Mongoose schemas — core: User, WineDefinition,
│ │ # Bottle, Cellar, Rack, WineVintageProfile/Price,
│ │ # WineRequest, WineCorrectionProposal, BottleImage,
│ │ # AuditLog, Country/Region/Appellation/Grape;
│ │ # community: Discussion*, Review, JournalEntry,
│ │ # WishlistItem, WineList, Follow, RestockAlert;
│ │ # infra: Notification, PushSubscription, SupportTicket,
│ │ # ImportSession, StripeWebhookEvent, WineEmbedding,
│ │ # ClimateDevice/Reading, OAuth*, McpActionLog, …
│ ├── routes/ # ~44 top-level route modules: auth (incl. Google SSO),
│ │ # cellars, bottles, wines (public read), racks, chat,
│ │ # stats, import, export, wineRequests, wineReports,
│ │ # discussions, reviews, journal, wishlist, wineLists,
│ │ # follows, recommendations, restockAlerts, climate,
│ │ # notifications, support, stripe, blog, og, sitemap,
│ │ # users, settings, images, health, superadmin, …
│ │ ├── admin/ # /api/admin/* — registry curation, taxonomy, users,
│ │ │ # tickets, proposals, fragmentation/cross-field queues
│ │ └── somm/ # /api/somm/* — maturity windows, prices, wine profiles
│ ├── mcp/ # MCP server: registry, tools (read/write/somm/admin),
│ │ # OAuth, action ledger with undo
│ ├── services/ # search (Meili), embedding (Voyage/OpenAI-compatible),
│ │ # vectorStore (wine vectors in MongoDB, compared in memory),
│ │ # aiChat (RAG), labelScan, enrichmentJob, audit,
│ │ # findOrCreateWine, imageProcessor, taxonomyMerge,
│ │ # registryHealthJob, crossFieldScan, statsService, …
│ ├── utils/ # normalize (dedup), cellarAccess, drinkWindow,
│ │ # grapeDisplay (regional names), nameChecks,
│ │ # crossFieldChecks, ratingUtils, maturityUtils, …
│ ├── data/ # Taxonomy reference JSON
│ └── seed-demo.js
├── frontend/
│ ├── src/
│ │ ├── api/ # Typed API client modules (one per resource)
│ │ ├── components/ # Reusable UI (Layout, Modal, BottleCard, rack grids, …)
│ │ ├── pages/ # App screens (cellars, bottles, stats, chat, admin, …)
│ │ ├── contexts/ # Auth, Theme, Notifications
│ │ ├── locales/ # i18n (Weblate-managed except en)
│ │ └── utils/
│ ├── nginx.conf # nginx config (SPA + /api/ proxy)
│ └── Dockerfile # Multi-stage: Node build → nginx-unprivileged
├── rembg/ # Python background-removal service
└── docker-compose.yml
服务
所有外部流量都通过 Traefik 进入(运行在共享的 web Docker 网络上,位于此 Compose 文件之外)。此 Compose 文件中的所有服务都仅供内部使用。
| 服务 | 主机端口 | 说明 |
|---|---|---|
| Traefik | 80 | 外部反向代理(外部) |
| nginx | 内部 | 提供 React SPA 服务 + 代理 /api/ |
| Backend | 内部 | Express REST API(端口 5000) |
| MongoDB | 内部 | 数据库(端口 27017) |
| Meilisearch | 内部 | 模糊搜索引擎(端口 7700) |
| rembg | 内部 | 背景移除(端口 5000) |
| Umami(+db) | 内部 | 可选分析功能 — --profile analytics |
在 Traefik 后运行
Cellarion 专为运行在共享 Docker 网络 web 上的 Traefik 反向代理之后而设计。Traefik 负责处理端口 80 上的传入 HTTP 流量(SSL 终止由上游的 Cloudflare 或类似服务处理)。
要求:
- 正在运行且已连接到名为
web的外部 Docker 网络的 Traefik 实例 - 启动 Cellarion 前必须已存在
web网络:docker network create web
前端服务在 docker-compose.yml 中声明了以下 Traefik 标签:
traefik.enable: "true"
traefik.docker.network: "web"
traefik.http.routers.cellarion.rule: "Host(`cellarion.app`)"
traefik.http.routers.cellarion.entrypoints: "web"
traefik.http.services.cellarion.loadbalancer.server.port: "8080"
上游端口是 8080(不是 80):前端镜像基于
nginxinc/nginx-unprivileged 构建,其中以非 root 用户运行的 nginx 无法绑定到低于 1024 的端口。
请更新 Host(...) 规则,使其匹配你自己的域名。
连接你的 AI(MCP)
Cellarion 内置了 Model Context Protocol 服务,因此 AI 助手可以通过对话读取和管理你的酒窖——“今晚该开哪瓶?”、“添加这六瓶酒”、“设置这个年份的适饮期”。
- 个人服务 —
https://cellarion.app/api/mcp(OAuth;管理你自己的酒窖,支持读取/写入/饮用权限范围;为相应角色提供侍酒师和管理员工具,并配有操作日志和undo_last) - 公共注册表服务 —
https://cellarion.app/api/mcp/public(无需注册;只读共享葡萄酒注册表) - 设置指南 — cellarion.app/connect-ai 提供适用于 Claude(web/Desktop/Code)和其他 MCP 客户端的可复制粘贴配置
- 还以
cellarion-mcp的形式发布在 npm 上,并收录于 官方 MCP 注册表(app.cellarion/cellarion、app.cellarion/wine-registry)
可自托管实例会从自身源站提供相同端点——/connect-ai 页面会自动生成适用于该实例的代码片段。
面向自托管用户的共享葡萄酒注册表(Registry Bridge)
自托管的 Cellarion 初始时没有葡萄酒注册表。Registry Bridge 会将它连接到 cellarion.app 上的共享注册表,一次同步一款葡萄酒:注册表中的匹配项会显示在添加酒瓶时的搜索结果中;你选中的葡萄酒会连同品鉴档案和适饮期一起复制到你自己的数据库;副本每周更新一次(你对副本所做的编辑会保留,且设置 REGISTRY_BRIDGE_REFRESH=off 可停止更新);用户提交的更正和建议内容会传回共享队列,并标记为由你的安装实例提交。注册表本身始终留在 cellarion.app 上——不提供批量下载或快照。
- 登录 cellarion.app,打开 Settings → Connect a self-hosted Cellarion,接受 Registry Data Terms 并创建密钥(每个账户可创建两个,密钥只显示一次)。
- 将以下两行添加到你的
.env文件,然后重启后端:
REGISTRY_BRIDGE_URL=https://cellarion.app
REGISTRY_BRIDGE_KEY=cbr_…
配额、每月导入窗口和协议详见 docs/registry-bridge.md;条款见 cellarion.app/terms。
核心概念
| 实体 | 说明 |
|---|---|
| WineDefinition | 共享注册表中不区分年份的葡萄酒条目。由管理员管理;通过用户提交的葡萄酒请求、导入和侍酒师更正提案不断扩充,管理员可一键审核。 |
| Bottle | 用户的酒瓶:关联一个 WineDefinition,并添加年份、价格、评分、备注、酒架位置,以及开瓶/预留状态。 |
| Cellar | 由用户拥有的 Bottle 命名容器。可通过基于角色的访问权限与其他用户共享。 |
| Rack | Cellar 内可自定义的网格(最多 20×20,默认 8×4),用于放置实体酒瓶,并提供 3D 房间视图。 |
| WineVintageProfile | 由侍酒师整理的各款葡萄酒及年份的适饮期(成熟度)数据,是适饮提醒的数据来源。 |
| WineRequest | 用户提交的葡萄酒建议。管理员会审核并通过创建 WineDefinition 来处理。 |
| Taxonomy | 由管理员管理的国家、产区、法定产区和葡萄品种(含地区显示名称),用于避免自由文本条目不断增加。 |
| Notification | 应用内和推送通知,用于告知葡萄酒请求已处理、图片已批准、酒窖已共享、库存已补充等事件。 |
| SupportTicket | 用户支持工单,并跟踪管理员的回复。 |
| WineReport | 用户提交的葡萄酒质量报告(信息错误、重复条目、不当内容)。 |
用户角色
| Role | Description |
|---|---|
| user | 标准用户——管理自己的酒窖、酒瓶和请求 |
| sommelier | 为共享注册表整理适饮期、定价数据、品鉴档案和修正提案 |
| admin | 完整访问权限——葡萄酒库、分类体系、用户管理、图片审核、注册表质量队列、审计日志 |
| super admin | 平台级访问权限——系统监控、服务健康状态、速率限制、AI 配置、嵌入管理 |
API 概览
后端公开约 65 个路由模块;以下是核心接口,并非完整参考。葡萄酒注册表的读取接口(/api/wines、公开葡萄酒页面、OG 图片、站点地图)无需身份验证;所有个人数据都需要 JWT(Authorization: Bearer <token>)。
身份验证 — /api/auth
| Method | Path | Description |
|---|---|---|
| POST | /register | 创建账号(如果已配置 Mailgun,则发送验证邮件) |
| POST | /login | 登录并返回 JWT(如果已配置 Mailgun,则邮箱验证通过前无法登录) |
| GET | /google → /google/callback | Google SSO(设置了 GOOGLE_CLIENT_ID/SECRET 时) |
| GET | /oidc → /oidc/callback | 使用你自己的提供方进行 OIDC SSO(设置了 OIDC_* 变量时) |
| GET | /sso/providers | 此部署已配置的 SSO 提供方 |
| GET | /verify-email?token= | 验证邮箱地址;成功后返回 JWT |
| POST | /resend-verification | 重新发送验证邮件 |
| POST | /forgot-password | 请求发送密码重置邮件 |
| POST | /reset-password | 使用令牌重置密码 |
酒窖 — /api/cellars (需要身份验证)
| Method | Path | Description |
|---|---|---|
| GET | / | 列出用户的酒窖 |
| POST | / | 创建酒窖 |
| GET | /:id | 获取酒窖及其中的酒瓶 |
| PUT | /:id | 更新酒窖 |
| DELETE | /:id | 删除酒窖 |
| GET | /:id/statistics | 汇总统计数据 |
酒瓶 — /api/bottles (需要身份验证)
| Method | Path | Description |
|---|---|---|
| POST | / | 向酒窖中添加酒瓶 |
| PUT | /:id | 更新酒瓶 |
| DELETE | /:id | 移除酒瓶 |
| POST | /:id/consume · /:id/open · pour/close | 饮用跟踪生命周期 |
| POST | /import/validate | 验证导入数据并匹配葡萄酒(仅可读取注册表) |
| POST | /import/confirm | 根据已验证的导入数据创建酒瓶 |
对 /api/bottles 和 /api/racks 的写入请求可以携带可选的 Idempotency-Key 标头:使用相同密钥重复请求时会返回首次请求的结果,而不会再次执行(保留 8 天)。离线队列中的写入请求还会发送前置条件(ifActive、ifUnchanged、expectOccupant/expectFrom/expectTo、?expect=);如果此期间酒窖已发生变化,则返回 409。
离线 — /api/offline (需要身份验证)
| Method | Path | Description |
|---|---|---|
| GET | /snapshot | 将用户的酒窖(个人及共享)、未饮用的酒瓶和酒架汇总为一个文档,供离线模式使用(no-store,受速率限制) |
葡萄酒注册表 — /api/wines
公开读取。普通用户的搜索结果数量受限;admin/sommelier 可浏览全部内容。
| Method | Path | Description |
|---|---|---|
| GET | / | 搜索/筛选葡萄酒。参数:search、type、country、region、grapes、sort、limit、offset |
| GET | /:id | 获取单个葡萄酒定义(葡萄品种包含区域标签 displayName) |
聊天 — /api/chat (需要身份验证)
| Method | Path | Description |
|---|---|---|
| POST | / | 向 AI 酒窖聊天发送问题(RAG 流程) |
MCP — /api/mcp 和 /api/mcp/public
参见连接你的 AI。个人服务使用带动态客户端注册的 OAuth 2.0;公共服务无需身份验证。
社区(需要身份验证)
/api/discussions、/api/reviews、/api/journal、/api/wishlist、/api/wine-lists、/api/follows、/api/recommendations、/api/restock-alerts — 酒单、评论、讨论、日志、心愿单、关注和补货提醒。
气候 — /api/climate(需要身份验证)
注册酒窖传感器、接收读数(兼容 Home Assistant 的令牌身份验证),以及按酒窖划分的仪表板。
通知 / 统计 / 支持 / 葡萄酒报告 / 葡萄酒请求(需要身份验证)
与之前相同:/api/notifications、/api/stats/overview、/api/support、/api/wine-reports、/api/wine-requests。
Sommelier — /api/somm/*(需要 somm 或 admin 角色)
| 方法 | 路径 | 描述 |
|---|---|---|
| GET/PUT | /maturity | 按葡萄酒和年份整理适饮期阶段 |
| GET/POST | /prices | 整理定价数据 |
| PUT | /profile/:wineId | 更正葡萄酒的品鉴档案、类型和葡萄品种 |
管理员 — /api/admin/*(需要 admin 角色)
葡萄酒定义及合并、葡萄酒请求、更正提案、分类体系(包括葡萄品种的地区名称)、注册表质量队列(重复项、碎片化、名称检查、跨字段检查)、图片、用户、工单、审计日志。
超级管理员 — /api/superadmin/*(仅限超级管理员)
平台分析、速率限制、AI 模型/提示词配置、公告横幅、嵌入任务。
环境变量
将 .env.example 复制为 .env——**其中的注释已完整说明,是权威参考。**核心值:
| 变量 | 必填 | 默认值 | 说明 |
|---|---|---|---|
JWT_SECRET | 是 | — | 用于签署 JWT 的长随机字符串 |
MEILI_MASTER_KEY | 是 | — | 用于 Meilisearch 身份验证的长随机字符串 |
MONGO_URI | 否 | mongodb://mongo:27017/winecellar | MongoDB 连接 |
ACCESS_TOKEN_EXPIRES_IN | 否 | 15m | 访问令牌 TTL(会话通过轮换的 30 天 Cookie 刷新) |
PORT | 否 | 5000 | 后端端口 |
FRONTEND_URL | 否 | http://localhost | CORS 来源——生产环境中请设置为你的域名 |
MEILI_URL | 否 | http://meilisearch:7700 | Meilisearch URL |
REMBG_URL | 否 | http://rembg:5000 | 背景移除服务 |
ANTHROPIC_API_KEY | 否 | — | 启用酒标扫描和 AI 酒窖聊天(获取密钥) |
VOYAGE_API_KEY | 否 | — | AI 酒窖聊天的嵌入功能必需(获取密钥) |
SUPER_ADMIN_EMAIL | 否 | — | 超级管理员账户的电子邮件地址 |
MAILGUN_API_KEY / MAILGUN_DOMAIN | 否 | — | 启用电子邮件验证和事务性邮件 |
可选集成(每项均在 .env.example 中有完整说明):
| 分组 | 变量 | 启用的功能 |
|---|---|---|
| 自托管 AI | AI_PROVIDER、OPENAI_BASE_URL、OPENAI_API_KEY、AI_MODEL、AI_VISION_MODEL、EMBEDDING_PROVIDER、EMBEDDING_MODEL、EMBEDDING_DIMENSION、… | 使用任何兼容 OpenAI 的端点替代 Anthropic/Voyage——参见自托管 AI(兼容 OpenAI 的端点) |
| Google SSO | GOOGLE_CLIENT_ID、GOOGLE_CLIENT_SECRET、GOOGLE_CALLBACK_URL | 使用 Google 登录 |
| OIDC SSO | 必填: OIDC_CLIENT_ID、OIDC_CLIENT_SECRET、OIDC_ISSUER、OIDC_AUTHORIZATION_URL、OIDC_TOKEN_URL、OIDC_USERINFO_URL · 可选:OIDC_PROVIDER_NAME、OIDC_CALLBACK_URL、OIDC_SCOPES、OIDC_TRUST_EMAIL_VERIFIED | 使用你自己的身份提供方登录(Pocket ID、Authentik、Keycloak、Zitadel、Authelia)。可与 Google 同时使用;端点取自提供方的 /.well-known/openid-configuration。注册客户端时选择 client_secret_post——凭据放在令牌请求正文中,而不是 HTTP Basic 中 |
| Registry Bridge | REGISTRY_BRIDGE_URL、REGISTRY_BRIDGE_KEY、REGISTRY_BRIDGE_REFRESH | 从 cellarion.app 上的共享注册表搜索并复制葡萄酒到可自托管的安装;REGISTRY_BRIDGE_REFRESH=off 可确保副本保持复制时的原样——参见共享葡萄酒注册表(面向自托管用户):Registry Bridge |
| 支持者付款 | STRIPE_SECRET_KEY、STRIPE_WEBHOOK_SECRET、STRIPE_{SUPPORTER,PATRON,BENEFACTOR}_PRICE_ID + _ANNUAL_ 变体 | 为可选层级启用 Stripe Checkout。每个层级使用独立的 Stripe Product(一个月付价格和一个年付价格),否则客户门户无法提供层级切换。每个价格变量接受逗号分隔的列表——当前价格放在第一位,后面列出仍有活跃订阅者的任何已停用价格 |
| 推送通知 | VAPID_PUBLIC_KEY、VAPID_PRIVATE_KEY、VAPID_EMAIL | 为适饮提醒和活动启用 Web 推送 |
| 气候监测 | CLIMATE_RETENTION_DAYS、CLIMATE_MAX_DEVICES_PER_USER、CLIMATE_MAX_READINGS_PER_DAY、… | 传感器数据接收限制 + GDPR 数据保留 |
| 分析 | UMAMI_DB_PASSWORD、UMAMI_APP_SECRET、VITE_UMAMI_URL、VITE_UMAMI_WEBSITE_ID | 自托管、无 Cookie 的 Umami(--profile analytics;VITE_* 为构建时变量) |
| 运维 | TRUST_PROXY_HOPS、COOKIE_SECURE、BACKEND_URL、MEILI_SEARCH_KEY、LOG_LEVEL、AUDIT_TTL_DAYS、VITE_SITE_URL、INDEXNOW_KEY、SUPER_ADMIN_IPS | 代理信任、Cookie、日志记录、SEO、审计数据保留 |
AI 酒窖聊天
AI 聊天功能需要两项服务:
- Anthropic Claude(
ANTHROPIC_API_KEY)——生成以你的酒窖为依据的对话回复 - Voyage AI(
VOYAGE_API_KEY)——创建葡萄酒嵌入向量,用于语义搜索 葡萄酒向量存储在 MongoDB 中,由后端自行比对,无需运行单独的向量数据库。(在 v1.241 之前,Cellarion 使用 Qdrant;有关现有向量的一次性复制,请参阅 升级。)
配置好这两项服务后,用户就可以用自然语言询问自己的藏酒(食物搭配、场合推荐、酒窖洞察)。系统只会显示用户实际拥有的葡萄酒,不会生成凭空捏造的推荐。
所有用户共用一个每日使用额度,不受支持者等级影响;超级管理员可以配置此额度(默认每天 50 个问题)。
自托管 AI(兼容 OpenAI 的端点)
如果自托管用户不想使用 Anthropic API 密钥,可以将所有大模型功能(酒窖聊天、酒标扫描、导入查询、适饮期/价格/档案建议)指向任何支持 OpenAI 聊天补全 API 的端点,例如 Ollama、LM Studio、vLLM、LiteLLM 或 OpenAI 本身:
AI_PROVIDER=openai
OPENAI_BASE_URL=http://host.docker.internal:11434/v1 # your endpoint's /v1 root
AI_MODEL=llama3.1:70b # any model your server hosts
AI_VISION_MODEL=qwen2.5-vl:32b # optional — used for label scanning
葡萄酒嵌入向量(酒窖聊天中负责语义搜索的部分)也可以用同样的方式改为使用 Voyage AI 以外的服务:
EMBEDDING_PROVIDER=openai
EMBEDDING_MODEL=nomic-embed-text # any embedding model your server hosts
EMBEDDING_DIMENSION=768 # MUST be that model's real vector size
# EMBEDDING_BASE_URL / EMBEDDING_API_KEY default to OPENAI_BASE_URL / OPENAI_API_KEY
注意:
- 两项切换都需主动启用:不启用时,所有设置保持不变,默认仍使用 Anthropic + Voyage;两项服务也可以分别切换(例如,本地大模型 + Voyage 嵌入向量)。
host.docker.internal开箱即用的条件仅限 Docker Desktop(Windows/macOS)。在 Linux 服务器上,需要在 compose 文件的后端服务中添加extra_hosts: ["host.docker.internal:host-gateway"],或将OPENAI_BASE_URL指向主机的局域网 IP 或 compose 网络中的服务。- AI 使用额度(每用户/全局每日上限、每次导入上限、聊天每日限制)原本用于限制 Anthropic 的付费开销。使用自有硬件时,这些限制仍然生效;如果不希望本地端点受额度限制,可在 SuperAdmin → Rate limits 中调高或停用额度(
0/-1= 不限)。 - 管理面板中的 Claude 模型设置在 openai 模式下会被忽略(模型由
AI_MODEL指定);可配置的 AI 提示词仍然生效。 - 标签扫描需要支持视觉功能的模型。提取质量很大程度上取决于你托管的模型——与 Claude 相比,较小的本地模型会误读更多酒标。
- 存储的向量包含嵌入维度。更改
EMBEDDING_PROVIDER、EMBEDDING_MODEL或EMBEDDING_DIMENSION后,请运行一次完整的嵌入任务(SuperAdmin → AI)——该任务会原位重新为每款葡萄酒生成嵌入向量(期间搜索仍可正常使用),随后删除旧向量。每个返回的向量都会根据EMBEDDING_DIMENSION进行校验,因此错误的值会明确报错,而不会破坏搜索。 - **多用户实例的隐私提示:**随附的隐私政策将 Anthropic 和 Voyage AI 列为 AI 子处理方。将这些变量指向你自己的本地端点(在自有硬件上运行 Ollama/vLLM)即可完全移除第三方 AI 处理;但如果指向远程第三方服务(例如 OpenAI 或托管代理),你有责任相应更新实例的隐私政策并取得用户同意。
电子邮件验证
同时设置 MAILGUN_API_KEY 和 MAILGUN_DOMAIN 后,将启用电子邮件验证:
- 新用户注册后会收到验证链接,点击链接之前无法登录。
- 链接会在 24 小时后过期。登录页面和
/verify-email页面均提供重新发送选项。 - 如果未配置 Mailgun,注册时会立即发放令牌,行为与之前相同。
**现有用户:**在运行中的实例启用验证后,现有账号的 emailVerified 将为 false,用户也会被锁定。在 MongoDB shell 中运行以下命令一次即可恢复访问权限:
db.users.updateMany({ emailVerified: { $exists: false } }, { $set: { emailVerified: true } })
瓶子导入
用户可以从其他葡萄酒酒窖应用导入瓶子(Vivino——包括饮用历史、CellarTracker、Ploc——包括酒架位置和购买历史,或任何通用 CSV)。导入流程:
- 上传 — 拖入 CSV 文件;系统会自动检测来源格式,并映射到标准架构
- 验证 — 使用模糊搜索(Meilisearch + MongoDB 文本搜索 + 规范化键查找),将每项与葡萄酒库匹配,并计算综合相似度得分
- 审核 — 用户可查看匹配结果:精确匹配(自动选中)、模糊匹配(从候选项中选择)和未匹配项(手动搜索或跳过)
- 导入 — 将已确认的项目作为瓶子创建到目标酒窖中(在你确认之前,不会写入共享注册表)
导入会话会持久保存,以便用户在流程中断后继续操作。可从任意酒窖的更多菜单(⋯ → 导入瓶子)进入导入功能。需要编辑者或所有者权限。
主导入 JSON 格式
也可以将瓶子作为 JSON 导入。每项支持:
{
"wineName": "Albe",
"producer": "G.D. Vajra",
"vintage": "2019",
"country": "Italy",
"region": "Piedmont",
"appellation": "Barolo",
"type": "red",
"price": 299,
"currency": "SEK",
"bottleSize": "750ml",
"quantity": 2,
"purchaseDate": "2024-03-15",
"purchaseLocation": "Systembolaget",
"notes": "Beautiful nebbiolo",
"rating": 4.2,
"ratingScale": "5",
"rackName": "Rack A",
"rackPosition": 5,
"addToHistory": false
}
要直接导入到历史记录中(已饮用的瓶子),请添加:
{
"addToHistory": true,
"consumedReason": "drank",
"consumedAt": "2025-12-24",
"consumedNote": "Opened for Christmas",
"consumedRating": 4.5,
"consumedRatingScale": "5",
"dateAdded": "2024-06-01"
}
酒窖导出
酒窖所有者可以通过酒窖的更多菜单(⋯ → 导出)或设置导出数据。可导出为 JSON,也可导出为 ZIP,其中还包含你上传的瓶子图片。导出内容包括带有酒架位置(rackName、rackPosition)的瓶子、酒架几何信息、3D 房间布局和你自己的评价。JSON 格式可直接重新导入。(根据 GDPR,完整账户导出——涵盖你的每一类数据——可在设置 → 隐私中获取。)
葡萄酒去重
创建葡萄酒时,系统会使用以下方法检查是否存在近似重复项:
- Levenshtein 距离(40%)——字符级相似度
- Trigram Jaccard(30%)——重叠的 3 字符序列
- Token Jaccard(30%)——移除葡萄酒领域停用词后的词级相似度
得分:名称 × 0.45 + 生产商 × 0.45 + 产区 × 0.10
高于阈值(默认为 0.75)的候选项会作为警告显示,并提供“使用此项”选项。在此之下,还有管理员端的质量检查队列——重复项分组、生产商碎片化匹配对、名称检查,以及跨字段领域检查(例如,实际为产区的生产商、实际为国家的地区……);每次扫描都会根据实时分类体系重新测试。
测试
前端
cd frontend && npm test
使用 React Testing Library 运行 Vitest(vitest run——单次非监听模式运行)。不要添加 CRA 风格的标志,例如 --watchAll=false——前端使用 Vite 构建,而不是 Create React App,未知标志会导致错误。
后端
cd backend && npm test
使用 Jest——约 124 个测试套件,涵盖身份验证中间件、酒窖访问控制、葡萄酒规范化/去重、注册表检查、评分转换、饮用期限、MCP 工具等。
提交拉取请求前,请运行两套测试。测试失败的 PR 不会合并。
参与贡献
欢迎贡献——包括错误修复、功能、文档和翻译。CONTRIBUTING.md 提供完整指南:如何进行设置、分支和测试工作流、代码库遵循的规范、每项功能都必须通过的个人数据检查清单,以及如何签署提交(Cellarion 使用 Developer Certificate of Origin,因此请使用 git commit -s 提交)。简要步骤如下:
- Fork 仓库,并从
main创建分支(feat/、fix/、docs/……) - 完成更改并添加测试
- 运行两套测试:
cd frontend && npm test和cd backend && npm test - 在 Docker 中进行冒烟测试:
docker compose up --build,并使用你修改的功能 - 向
main发起拉取请求——模板会要求填写说明和检查清单
所有参与者都应遵守行为准则。
翻译
Cellarion 的界面由社区通过 Weblate 翻译——这是一个网页编辑器,无需 Git 或编写代码。开发者在维护代码的同时负责维护英文;其他所有语言均由志愿者翻译。
不同语言需要的帮助各不相同:
| 语言 | 所需帮助 |
|---|---|
| 法语、德语 | 需要审校者,而非译者。 这两种语言都已批量机器翻译,因此每个字段都已填写——需要做的是阅读现有译文并进行修正。需要处理的队列是 state:translated NOT state:approved,而不是未翻译队列。 |
| 瑞典语 | 需要母语读者。实际上已基本完成,但几乎没有经过审校。 |
| 爱沙尼亚语,或未列出的语言 | 需要从头开始翻译。请在 Weblate 中申请该语言并开始翻译——完成度约达 10% 后,该语言就会显示在应用中;在此之前,可以通过 ?lng=<code> 预览。 |
翻译指南、葡萄酒术语词汇表,以及语言如何结束测试阶段:TRANSLATING.md。
(请勿直接提交修改 translation.json 文件的拉取请求——en 除外。此类修改会与 Weblate 的双向同步冲突。)
报告漏洞
请勿公开提交安全问题。请使用 GitHub 的私密漏洞报告,或发送邮件至 [email protected],并在主题中注明“SECURITY”。响应时间、范围以及当前安全状况的真实说明,请参阅 SECURITY.md。
许可证
GNU Affero 通用公共许可证 v3.0(AGPL-3.0)
你可以自由使用、修改、自托管,甚至将此软件作为服务提供。唯一的条件,也是 Cellarion 选择 AGPL 而非 MIT 的原因:如果你为他人运行修改后的版本,就必须向他们提供修改后的源代码。
贡献内容也将以相同许可证授权;请参阅 CONTRIBUTING.md。
致谢
此代码库是在 Anthropic 的 Claude Code 协助下共同开发的。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。