feat: 方案设计、数据库和接口设计基本完成
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
# MinIO 入门:本地对象存储的部署与使用
|
||||
|
||||
> 面向 Atlas Seller Kit 的学习文档。本文解释 MinIO 是什么、本项目为什么在本地开发使用它、如何用 Docker 部署,以及如何按本项目的 `object_key` 约定访问文件。`object_key` 与 URL 的概念区分见[对象存储入门](./object-storage-object-key.md)。
|
||||
|
||||
## 1. MinIO 是什么
|
||||
|
||||
MinIO 是一个开源的对象存储服务器,完整实现了 **Amazon S3 API**。它可以理解为"你自己跑的一个 S3":
|
||||
|
||||
| 项目 | MinIO | 云端对象存储(如七牛 Kodo) |
|
||||
|---|---|---|
|
||||
| 形态 | 单个容器/二进制,跑在自己机器或服务器上 | 托管服务,开箱即用 |
|
||||
| 接口 | S3 兼容 API | S3 兼容 API(基础能力子集) |
|
||||
| 数据位置 | 本机 Docker 卷 | 云端 |
|
||||
| 运维 | 自己负责(本地开发无所谓) | 云厂商负责 |
|
||||
| 费用 | 免费 | 按存储/流量计费 |
|
||||
|
||||
它只有三个核心概念(与所有 S3 兼容存储通用):
|
||||
|
||||
- **Bucket(存储桶)**:文件的顶层容器,例如 `atlas-dev-assets`;
|
||||
- **Object Key(对象键)**:桶内文件的唯一键,例如 `workspaces/ws_123/products/p_456/original/asset_789.jpg`;
|
||||
- **预签名 URL**:带签名和过期时间的临时访问地址,用于把私有文件的读写权限短暂借给浏览器。
|
||||
|
||||
## 2. 本项目为什么用 MinIO
|
||||
|
||||
Atlas 的技术选型是:**本地开发用 MinIO,staging/production 用七牛 Kodo**(见[技术栈与部署](../architecture/technology-and-deployment.md)第 4 节)。选择这个组合的原因:
|
||||
|
||||
1. **代码同构**:两者都讲 S3 方言。应用代码只写一套 S3 客户端逻辑,本地和生产只差 endpoint、凭证、桶名这几个配置项,不差代码。
|
||||
2. **行为可验证**:私有桶权限、预签名 URL 过期、ETag/Content-Type、MIME 校验这些行为,在本地 MinIO 上验证过的逻辑,到 Kodo 上基本原样成立。如果本地直接读写磁盘目录,这些逻辑完全测不到。
|
||||
3. **可随时重建**:MinIO 数据在 Docker 卷里,删掉卷重启就是全新的存储,适合配合版本化 seed 数据做开发。
|
||||
4. **成本为零**:开发阶段不产生云存储费用,也不会误用生产 Bucket。
|
||||
|
||||
注意边界:MinIO 只进**本地开发环境**。首期不在普通单台云服务器上自建生产 MinIO——磁盘冗余、备份、故障恢复、TLS、监控都要自己做,"部署了 MinIO"不等于获得托管对象存储的可靠性。
|
||||
|
||||
## 3. 用 Docker 部署本地 MinIO
|
||||
|
||||
### 3.1 单命令快速启动
|
||||
|
||||
只想临时体验时:
|
||||
|
||||
```bash
|
||||
docker run -d --name atlas-minio \
|
||||
-p 9000:9000 -p 9001:9001 \
|
||||
-v atlas-minio-data:/data \
|
||||
minio/minio server /data --console-address ":9001"
|
||||
```
|
||||
|
||||
两个端口各有用途:
|
||||
|
||||
| 端口 | 用途 |
|
||||
|---|---|
|
||||
| `9000` | **S3 API 端口**。应用代码(SDK、`mc` 客户端)访问这个。 |
|
||||
| `9001` | **Web Console 端口**。浏览器打开 `http://localhost:9001` 管理文件。 |
|
||||
|
||||
### 3.2 接入项目的 Docker Compose
|
||||
|
||||
正式开发应把它写进项目根目录的 `docker-compose.yml`,与 `postgres`、`redis` 并列:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
minio:
|
||||
image: minio/minio:latest # 团队建议固定到具体 RELEASE tag,避免本地环境漂移
|
||||
command: server /data --console-address ":9001"
|
||||
ports:
|
||||
- "9000:9000" # S3 API
|
||||
- "9001:9001" # Web Console
|
||||
environment:
|
||||
MINIO_ROOT_USER: atlas-dev
|
||||
MINIO_ROOT_PASSWORD: atlas-dev-secret # 仅本地开发用,不得用于任何共享环境
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
minio_data:
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD` 是这个实例的 root 账号,仅本地使用;真正的云端密钥永远走环境变量或密钥管理,不提交 Git。
|
||||
- `-v minio_data:/data` 把数据落在命名卷里;`docker compose down` 不丢数据,`docker compose down -v` 会连数据一起清掉(重置开发环境的常用手段)。
|
||||
- 建议把这两个值放进 `.env`(该文件已在 `.gitignore` 中),compose 里用 `MINIO_ROOT_USER: ${MINIO_ROOT_USER}` 引用。
|
||||
|
||||
### 3.3 应用读取的配置约定
|
||||
|
||||
NestJS API/Worker 通过环境变量拿到连接信息,本地 `.env` 示例:
|
||||
|
||||
```bash
|
||||
# 对象存储(本地 MinIO / 生产 Kodo 只换这一组值)
|
||||
S3_ENDPOINT=http://localhost:9000
|
||||
S3_REGION=us-east-1 # MinIO 不校验 region,任意合法值即可
|
||||
S3_BUCKET=atlas-dev-assets
|
||||
S3_ACCESS_KEY=atlas-dev
|
||||
S3_SECRET_KEY=atlas-dev-secret
|
||||
S3_FORCE_PATH_STYLE=true # MinIO 必须用 path-style,见第 6.3 节
|
||||
```
|
||||
|
||||
## 4. 创建 Bucket
|
||||
|
||||
MinIO 的桶默认是**私有**的,符合本项目"Bucket 默认私有"的规则。两种创建方式任选。
|
||||
|
||||
### 4.1 Web Console(直观)
|
||||
|
||||
1. 打开 `http://localhost:9001`,用 root 账号登录;
|
||||
2. 左侧 **Object Browser → Create Bucket**;
|
||||
3. 名称填 `atlas-dev-assets`,保持默认(不勾选公开访问),确认创建。
|
||||
|
||||
### 4.2 mc 命令行(可脚本化)
|
||||
|
||||
`mc` 是 MinIO 官方 CLI,也可以用来管理任何 S3 兼容存储。用一次性容器执行,不必本机安装:
|
||||
|
||||
```bash
|
||||
# 1. 给本地实例起个别名
|
||||
docker run --rm --network host minio/mc alias set local http://localhost:9000 atlas-dev atlas-dev-secret
|
||||
|
||||
# 2. 创建桶
|
||||
docker run --rm --network host minio/mc mb local/atlas-dev-assets
|
||||
|
||||
# 3. 确认桶是私有的(输出应为 none,即不允许匿名访问)
|
||||
docker run --rm --network host minio/mc anonymous get local/atlas-dev-assets
|
||||
```
|
||||
|
||||
首次启动时"确保桶存在"也可以由 API 启动逻辑完成(本地环境 `head bucket` 失败就 `create bucket`),这样新同事 `docker compose up` 后无需手工步骤。
|
||||
|
||||
## 5. 日常使用
|
||||
|
||||
### 5.1 Console 里的常见操作
|
||||
|
||||
| 操作 | 位置 |
|
||||
|---|---|
|
||||
| 浏览/搜索对象 | Object Browser,按前缀逐级进入 |
|
||||
| 手动上传/下载 | 进入桶后右上角 Upload / 对象行 Download |
|
||||
| 查看对象元数据 | 对象行右侧菜单 → Object info(大小、ETag、Content-Type) |
|
||||
| 生成临时分享链接 | 对象行菜单 → Share(本质是预签名 GET URL,可设过期时间) |
|
||||
| 管理访问密钥 | Identity → Access Keys(可给本地工具发独立密钥,不共用 root) |
|
||||
|
||||
### 5.2 mc 常用命令速查
|
||||
|
||||
```bash
|
||||
alias set local http://localhost:9000 USER PASS # 配置连接别名
|
||||
mb local/atlas-dev-assets # 创建桶
|
||||
ls local/atlas-dev-assets/workspaces/ # 列出前缀
|
||||
cp ./test.jpg local/atlas-dev-assets/tmp/test.jpg # 上传
|
||||
cp local/atlas-dev-assets/tmp/test.jpg ./ # 下载
|
||||
stat local/atlas-dev-assets/tmp/test.jpg # 查看元数据
|
||||
rm local/atlas-dev-assets/tmp/test.jpg # 删除对象
|
||||
du local/atlas-dev-assets # 桶占用
|
||||
```
|
||||
|
||||
### 5.3 应用代码访问(NestJS / Node)
|
||||
|
||||
本项目统一使用 AWS S3 SDK v3 指向自定义 endpoint,本地 MinIO 与生产 Kodo 走同一条代码路径:
|
||||
|
||||
```ts
|
||||
import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
|
||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||
|
||||
const s3 = new S3Client({
|
||||
endpoint: process.env.S3_ENDPOINT!, // http://localhost:9000
|
||||
region: process.env.S3_REGION!, // MinIO 任意值即可
|
||||
credentials: {
|
||||
accessKeyId: process.env.S3_ACCESS_KEY!,
|
||||
secretAccessKey: process.env.S3_SECRET_KEY!,
|
||||
},
|
||||
forcePathStyle: true, // MinIO 必须;Kodo 走虚拟主机式,配置驱动即可
|
||||
});
|
||||
|
||||
// 预签名上传 URL:前端拿到后直传,浏览器不保存永久密钥
|
||||
export async function presignUpload(objectKey: string, contentType: string) {
|
||||
const cmd = new PutObjectCommand({ Bucket: process.env.S3_BUCKET, Key: objectKey, ContentType: contentType });
|
||||
return getSignedUrl(s3, cmd, { expiresIn: 600 }); // 10 分钟
|
||||
}
|
||||
|
||||
// 预签名下载 URL:预览/下载私有文件
|
||||
export async function presignDownload(objectKey: string) {
|
||||
const cmd = new GetObjectCommand({ Bucket: process.env.S3_BUCKET, Key: objectKey });
|
||||
return getSignedUrl(s3, cmd, { expiresIn: 900 }); // 15 分钟
|
||||
}
|
||||
```
|
||||
|
||||
服务端直接读写(如 Worker 转存采集素材)则不经过预签名,直接调 `PutObjectCommand` 等。
|
||||
|
||||
### 5.4 与 Atlas 的 object_key 约定结合
|
||||
|
||||
本地桶内对象键沿用[技术文档第 4.2 节](../architecture/technology-and-deployment.md)的分层:
|
||||
|
||||
```text
|
||||
atlas-dev-assets/
|
||||
workspaces/{workspaceId}/
|
||||
watermarks/{assetId}/
|
||||
products/{productId}/original/{assetId}.jpg
|
||||
products/{productId}/processed/{assetId}.jpg
|
||||
products/{productId}/ai-generated/{assetId}.jpg
|
||||
imports/{taskId}/{filename}
|
||||
```
|
||||
|
||||
配套规则(详见[对象存储入门](./object-storage-object-key.md)):
|
||||
|
||||
- `media_assets` 表保存 `storage_provider='local_minio'` + `bucket` + `object_key`,**任何 URL 不入库**;
|
||||
- 预览/下载 URL 由服务端按 `object_key` 短时签发,过期即失效;
|
||||
- 来源站的原始图片 URL 只保存在采集证据里,转存成功后文件身份以 `object_key` 为准。
|
||||
|
||||
## 6. 常见问题
|
||||
|
||||
### 6.1 SDK 报错找不到桶或 DNS 异常
|
||||
|
||||
MinIO 默认不支持 `bucket.endpoint` 虚拟主机域名形式,SDK 必须开启 **path-style**(地址形如 `http://localhost:9000/atlas-dev-assets/...`)。AWS SDK v3 对应 `forcePathStyle: true`。该项目配置驱动这一点:本地为 `true`,Kodo 按其要求配置。
|
||||
|
||||
### 6.2 预签名 URL 在浏览器里打不开
|
||||
|
||||
预签名 URL 的 host 就是 `S3_ENDPOINT`。`http://localhost:9000` 只在**本机浏览器**可用;如果要给局域网同事访问,endpoint 需要用局域网 IP 并让 MinIO 监听对应地址。签名与 host 绑定,换了 host 签名即失效。
|
||||
|
||||
### 6.3 上传后 Content-Type 或文件名不对
|
||||
|
||||
预签名 `PutObjectCommand` 时设置的 `ContentType` 必须与前端实际上传时携带的 `Content-Type` 一致,否则 S3 签名校验会拒绝。项目在 Worker/服务端二次校验 MIME 与魔数,不信任浏览器声明。
|
||||
|
||||
### 6.4 想清空本地存储重来
|
||||
|
||||
```bash
|
||||
docker compose down -v # -v 连同 minio_data 卷一起删除
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
然后重建桶(或让应用启动逻辑自动创建),即可配合 seed 数据得到干净环境。
|
||||
|
||||
### 6.5 时钟偏差报错(RequestTimeTooSkewed)
|
||||
|
||||
S3 签名对时间敏感。宿主机睡眠/恢复后 Docker 容器时钟可能漂移,重启容器或同步系统时间即可。
|
||||
|
||||
## 7. 本项目边界
|
||||
|
||||
- MinIO 仅用于 **local** 环境;staging/production 使用七牛 Kodo 独立 Bucket,严禁混用。
|
||||
- Bucket 保持私有;原图、水印模板图、AI 生成图和导入文件不设公开读。
|
||||
- 浏览器只持有短时上传/下载凭证;永久密钥只存在于服务端环境配置。
|
||||
- 对象物理删除前必须确认无数据库引用;由后台清理任务在软删除保留期结束后执行。
|
||||
|
||||
相关文档:[对象存储与 object_key](./object-storage-object-key.md)、[技术栈、异步任务与部署方案](../architecture/technology-and-deployment.md)、[浏览器采集插件需求](../requirements/collector-browser-extension.md)。
|
||||
@@ -0,0 +1,127 @@
|
||||
# 对象存储入门:为什么数据库保存 `object_key`
|
||||
|
||||
> 面向 Atlas Seller Kit 的学习文档。本文解释对象存储、`object_key`、来源 URL 与预签名 URL 的不同,以及本项目应如何保存和访问商品素材。
|
||||
|
||||
## 1. 一句话结论
|
||||
|
||||
**对象存储 URL 是“此刻如何访问文件”,`object_key` 是“文件在存储桶中的稳定身份”。**
|
||||
|
||||
Atlas 的数据库以 `storage_provider + bucket + object_key` 标识已转存的文件;不以 URL 作为文件主数据,也不长期保存带授权参数的访问 URL。
|
||||
|
||||
## 2. 用一张商品图片理解三个概念
|
||||
|
||||
同一张商品原图在生产环境可能保存为:
|
||||
|
||||
```text
|
||||
storage_provider: qiniu_kodo
|
||||
bucket: atlas-prod-assets
|
||||
object_key: workspaces/ws_123/products/p_456/original/asset_789.jpg
|
||||
```
|
||||
|
||||
它在不同环境、不同时间可以生成不同 URL:
|
||||
|
||||
```text
|
||||
本地 MinIO URL:
|
||||
http://localhost:9000/atlas-dev-assets/workspaces/ws_123/...
|
||||
|
||||
七牛分发 URL:
|
||||
https://cdn.example.com/workspaces/ws_123/...
|
||||
|
||||
短时私有访问 URL:
|
||||
https://cdn.example.com/workspaces/ws_123/...?
|
||||
e=1780000000&token=temporary-signature
|
||||
```
|
||||
|
||||
这些 URL 可能随环境、域名、CDN、签名和过期时间而变化;稳定不变的是存储桶和 `object_key`。
|
||||
|
||||
| 项目 | 对象存储 URL | `object_key` |
|
||||
|---|---|---|
|
||||
| 含义 | 当前通过网络读取/写入文件的地址 | Bucket 内文件的唯一定位键 |
|
||||
| 示例 | `https://cdn.example.com/a.jpg?token=...` | `workspaces/ws_123/.../a.jpg` |
|
||||
| 稳定性 | 域名、CDN、签名、环境变化时会变化 | 由 Atlas 生成并管理,作为长期主标识 |
|
||||
| 是否自带读取权限 | 可能带有预签名授权 | 不带授权,只是定位信息 |
|
||||
| 是否作为数据库主字段 | 否 | 是 |
|
||||
|
||||
## 3. Atlas 的正确资产记录
|
||||
|
||||
```text
|
||||
media_assets
|
||||
- id
|
||||
- workspace_id
|
||||
- storage_provider # local_minio / qiniu_kodo
|
||||
- bucket
|
||||
- object_key
|
||||
- checksum_sha256
|
||||
- content_type
|
||||
- byte_size
|
||||
- width / height
|
||||
- created_at
|
||||
```
|
||||
|
||||
图片、视频、水印模板和 AI 生成文件都遵循这个规则。SKU 与媒体的关联保存 `media_asset.id`,而不是图片名称、来源 URL 或对象存储 URL。
|
||||
|
||||
## 4. 不同 URL 分别应保存在哪里
|
||||
|
||||
### 4.1 来源 URL:可以保存,但只是采集证据
|
||||
|
||||
采集 1688、淘宝、天猫、Ozon 公开页时得到的图片链接是 `source_url`。它可能过期、防盗链、重定向或失效,因此只应与采集时间、来源页面、解析器版本一起保存在采集证据中。Worker 下载并转存成功后,正式素材身份切换为 `object_key`。
|
||||
|
||||
### 4.2 Atlas 访问 URL:临时生成,不长期保存
|
||||
|
||||
用户预览或下载私有素材时:
|
||||
|
||||
```text
|
||||
浏览器请求素材
|
||||
→ NestJS 验证当前用户是否有该 asset 的权限
|
||||
→ 根据 storage_provider + bucket + object_key 签发短时 GET URL
|
||||
→ 浏览器用该 URL 显示或下载文件
|
||||
```
|
||||
|
||||
短时 URL 会过期,包含签名时还代表临时授权,不能存到商品表、素材表、日志或前端长期缓存。
|
||||
|
||||
### 4.3 平台发布 URL:发布时派生并记录在快照
|
||||
|
||||
Ozon 导入要求图片为 HTTPS 公网可访问 URL。发布适配器可以从 Atlas 的 `object_key` 派生一个让 Ozon 可拉取的受控分发 URL;该 URL 仅用于这次平台请求,并与请求/响应一起保存在发布快照中,不能反过来替代素材的 `object_key`。
|
||||
|
||||
## 5. 上传和转存流程
|
||||
|
||||
### 用户手动上传
|
||||
|
||||
```text
|
||||
前端请求上传
|
||||
→ API 创建 asset ID 和 object_key
|
||||
→ API 签发短时上传 URL
|
||||
→ 浏览器直传 MinIO/Kodo
|
||||
→ API/Worker 校验 MIME、大小、哈希和图片尺寸
|
||||
→ media_assets 状态变为 ready
|
||||
```
|
||||
|
||||
浏览器不保存 MinIO/Kodo 的永久密钥。
|
||||
|
||||
### 浏览器插件采集
|
||||
|
||||
```text
|
||||
插件上报 source_url 和采集证据
|
||||
→ Worker 下载来源文件
|
||||
→ 校验并计算哈希
|
||||
→ 上传 MinIO/Kodo
|
||||
→ 写入 storage_provider + bucket + object_key
|
||||
→ 建立商品/SKU 媒体关联
|
||||
```
|
||||
|
||||
## 6. 为什么不能把 URL 当主数据
|
||||
|
||||
1. 本地 MinIO 切换到生产七牛 Kodo 时,域名与 URL 结构不同;`object_key` 不变,不需要批量改业务数据。
|
||||
2. 更换 CDN、绑定新域名或调整私有访问策略时,不需要重写数据库。
|
||||
3. 预签名 URL 到期后可重新生成;若保存它,只会积累失效链接。
|
||||
4. 私有原图不必永久公开,访问权限可按工作空间、商品和 SKU 动态校验。
|
||||
5. 去重、删除检查、生命周期清理都围绕稳定资产 ID、哈希和 `object_key` 进行。
|
||||
|
||||
## 7. 本项目的边界
|
||||
|
||||
- 本地开发使用 MinIO;共享测试与生产使用七牛 Kodo。
|
||||
- Bucket 默认私有;原图、水印、AI 图和导入文件不长期公开。
|
||||
- `object_key` 本身不是访问凭据,但也不应作为绕过权限校验的下载入口。
|
||||
- 对象删除前必须确认没有有效数据库引用;软删除或保留期结束后再由后台任务物理清理。
|
||||
|
||||
相关文档:[技术栈、异步任务与部署方案](../architecture/technology-and-deployment.md)、[浏览器采集插件需求](../requirements/collector-browser-extension.md)、[商品与 SKU 数据模型](../product/data-model.md)。
|
||||
@@ -0,0 +1,235 @@
|
||||
# 本地 PostgreSQL:原生安装与 Docker Compose 使用指南
|
||||
|
||||
> 面向 Atlas Seller Kit 的开发环境。本文讲两种等价的本地运行方式:使用已安装在 macOS 上的 PostgreSQL,或使用 Docker Compose 容器。二选一即可;不要让二者同时占用 `5432` 端口。本项目表结构以版本化 SQL migration 为准,而不是由 NestJS 自动同步。
|
||||
|
||||
## 1. 先理解三个层次
|
||||
|
||||
| 层次 | 谁负责 | 是否要手工做 |
|
||||
|---|---|---|
|
||||
| PostgreSQL 服务 | Homebrew/安装包,或 Docker Compose | 第一次启动服务 |
|
||||
| 数据库与角色 | 本地开发者的初始化步骤 | 每个本地环境一次 |
|
||||
| 表、索引、RLS、函数 | 版本化 migration | 用命令执行,不逐表手工创建 |
|
||||
|
||||
当前可执行结构文件是:
|
||||
|
||||
1. [`001_foundation.sql`](../architecture/sql/001_foundation.sql):账户、工作空间、平台、门店、水印、物流、定价和 RLS 基础。
|
||||
2. [`002_product-domain.sql`](../architecture/sql/002_product-domain.sql):采集箱、商品编辑、发布任务、在线商品镜像和 RLS。
|
||||
|
||||
它们只用于**全新的空数据库**的初始建表。项目初始化 NestJS 后,会将其纳入 migration runner;届时日常只运行统一的 `db:migrate` 命令,不再直接逐个执行 SQL 文件。
|
||||
|
||||
## 2. 共同的本地约定
|
||||
|
||||
| 名称 | 本地建议值 | 用途 |
|
||||
|---|---|---|
|
||||
| 数据库 | `atlas_dev` | 仅 Atlas 的本地开发数据 |
|
||||
| 迁移角色 | `atlas_migrator` | 拥有 `atlas` schema 和表,只执行 migration |
|
||||
| 应用角色 | `atlas_app_login` | 未来 NestJS API/Worker 的连接身份,不拥有表 |
|
||||
| 权限组 | `atlas_app` | 授予 API/Worker 所需的 DML 权限 |
|
||||
| schema | `atlas` | 所有 Atlas 表、类型、函数和策略 |
|
||||
|
||||
这三个角色的分工不能省略为“应用直接用管理员连接”:应用连接需要受 RLS 限制,迁移角色不能写入 API/Worker 的环境变量。完整原理见[数据库角色与 RLS 执行约束](../architecture/database-security.md)。
|
||||
|
||||
> 以下示例密码仅是本地占位符。不要复用到 staging/production,也不要提交 `.env` 文件。
|
||||
|
||||
## 3. 方式 A:使用已安装的本机 PostgreSQL
|
||||
|
||||
适合你已经通过 Homebrew 或 PostgreSQL.app 安装并日常使用 PostgreSQL 的情况。
|
||||
|
||||
### 3.1 确认服务和客户端
|
||||
|
||||
```bash
|
||||
psql --version
|
||||
pg_isready -h 127.0.0.1 -p 5432
|
||||
```
|
||||
|
||||
第二行输出 `accepting connections` 即可。若通过 Homebrew 安装而服务未运行,可查看服务状态:
|
||||
|
||||
```bash
|
||||
brew services list
|
||||
```
|
||||
|
||||
启动命令因你的 PostgreSQL 大版本而不同,通常可用:
|
||||
|
||||
```bash
|
||||
brew services start postgresql@16
|
||||
```
|
||||
|
||||
若实际安装的是其他版本,将 `16` 换成对应版本;也可以在 PostgreSQL.app 中直接启动数据库服务。
|
||||
|
||||
### 3.2 一次性创建角色和数据库
|
||||
|
||||
以具有建角色、建库权限的本机管理员账号连接。Homebrew 的默认管理员通常是当前 macOS 用户,因此先试:
|
||||
|
||||
```bash
|
||||
psql -d postgres
|
||||
```
|
||||
|
||||
进入 `psql` 后执行下面 SQL。将尖括号内容替换为你自己的**本地**密码:
|
||||
|
||||
```sql
|
||||
CREATE ROLE atlas_migrator LOGIN NOINHERIT NOBYPASSRLS PASSWORD '<local-migrator-password>';
|
||||
CREATE ROLE atlas_app NOLOGIN NOINHERIT NOBYPASSRLS;
|
||||
CREATE ROLE atlas_app_login LOGIN NOINHERIT NOBYPASSRLS PASSWORD '<local-app-password>';
|
||||
GRANT atlas_app TO atlas_app_login;
|
||||
|
||||
CREATE DATABASE atlas_dev OWNER atlas_migrator;
|
||||
```
|
||||
|
||||
退出后,用迁移角色创建 schema、安装扩展并建表:
|
||||
|
||||
```bash
|
||||
psql -X -v ON_ERROR_STOP=1 -h 127.0.0.1 -p 5432 -U atlas_migrator -d atlas_dev \
|
||||
-f docs/architecture/sql/001_foundation.sql
|
||||
|
||||
psql -X -v ON_ERROR_STOP=1 -h 127.0.0.1 -p 5432 -U atlas_migrator -d atlas_dev \
|
||||
-f docs/architecture/sql/002_product-domain.sql
|
||||
```
|
||||
|
||||
`-v ON_ERROR_STOP=1` 很重要:任一 SQL 出错时命令立即失败,而不是继续执行留下半成品结构。
|
||||
|
||||
### 3.3 授予应用角色权限
|
||||
|
||||
仍以 `atlas_migrator` 连接 `atlas_dev`,执行一次:
|
||||
|
||||
```sql
|
||||
GRANT USAGE ON SCHEMA atlas TO atlas_app;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA atlas TO atlas_app;
|
||||
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA atlas TO atlas_app;
|
||||
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE atlas_migrator IN SCHEMA atlas
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO atlas_app;
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE atlas_migrator IN SCHEMA atlas
|
||||
GRANT USAGE, SELECT ON SEQUENCES TO atlas_app;
|
||||
```
|
||||
|
||||
未来 API/Worker 使用的本地连接串形如:
|
||||
|
||||
```bash
|
||||
DATABASE_URL=postgresql://atlas_app_login:<local-app-password>@127.0.0.1:5432/atlas_dev?options=-c%20search_path%3Datlas%2Cpublic
|
||||
DATABASE_MIGRATOR_URL=postgresql://atlas_migrator:<local-migrator-password>@127.0.0.1:5432/atlas_dev?options=-c%20search_path%3Datlas%2Cpublic
|
||||
```
|
||||
|
||||
`DATABASE_MIGRATOR_URL` 仅供 migration 命令使用;API 与 Worker 只读取 `DATABASE_URL`。
|
||||
|
||||
## 4. 方式 B:使用 Docker Compose
|
||||
|
||||
适合希望 PostgreSQL 版本、参数和数据卷与团队统一,或不想管理本机 PostgreSQL 服务的情况。若本机 PostgreSQL 已占用 `5432`,Docker 映射到 `5433`,避免冲突。
|
||||
|
||||
项目初始化时将在仓库根目录提供 Compose 配置。在此之前,可将以下内容保存为本机未提交的 `compose.postgres.local.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: atlas-postgres
|
||||
ports:
|
||||
- "5433:5432" # 本机已有 PostgreSQL 时使用 5433;没有冲突可改为 5432:5432
|
||||
environment:
|
||||
POSTGRES_USER: atlas_admin
|
||||
POSTGRES_PASSWORD: atlas-admin-local-only
|
||||
POSTGRES_DB: postgres
|
||||
volumes:
|
||||
- atlas_postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U atlas_admin -d postgres"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
|
||||
volumes:
|
||||
atlas_postgres_data:
|
||||
```
|
||||
|
||||
启动并确认状态:
|
||||
|
||||
```bash
|
||||
docker compose -f compose.postgres.local.yml up -d
|
||||
docker compose -f compose.postgres.local.yml ps
|
||||
```
|
||||
|
||||
初始化角色和数据库:
|
||||
|
||||
```bash
|
||||
docker exec -it atlas-postgres psql -U atlas_admin -d postgres
|
||||
```
|
||||
|
||||
在交互终端执行第 3.2 节的角色与 `atlas_dev` 创建 SQL。然后从仓库根目录执行 migration;此处端口使用 Docker 映射出的 `5433`:
|
||||
|
||||
```bash
|
||||
psql -X -v ON_ERROR_STOP=1 -h 127.0.0.1 -p 5433 -U atlas_migrator -d atlas_dev \
|
||||
-f docs/architecture/sql/001_foundation.sql
|
||||
|
||||
psql -X -v ON_ERROR_STOP=1 -h 127.0.0.1 -p 5433 -U atlas_migrator -d atlas_dev \
|
||||
-f docs/architecture/sql/002_product-domain.sql
|
||||
```
|
||||
|
||||
最后按第 3.3 节给 `atlas_app` 授权。Docker 方式对应的连接串只有端口不同:
|
||||
|
||||
```bash
|
||||
DATABASE_URL=postgresql://atlas_app_login:<local-app-password>@127.0.0.1:5433/atlas_dev?options=-c%20search_path%3Datlas%2Cpublic
|
||||
```
|
||||
|
||||
日常停止/再次启动不会删除数据:
|
||||
|
||||
```bash
|
||||
docker compose -f compose.postgres.local.yml stop
|
||||
docker compose -f compose.postgres.local.yml up -d
|
||||
```
|
||||
|
||||
## 5. 检查表是否创建成功
|
||||
|
||||
用迁移角色连接后可执行:
|
||||
|
||||
```bash
|
||||
psql -h 127.0.0.1 -p 5432 -U atlas_migrator -d atlas_dev
|
||||
```
|
||||
|
||||
Docker 方式把 `5432` 改为 `5433`。进入后:
|
||||
|
||||
```sql
|
||||
SELECT schemaname, tablename, tableowner
|
||||
FROM pg_tables
|
||||
WHERE schemaname = 'atlas'
|
||||
ORDER BY tablename;
|
||||
|
||||
SELECT c.relname AS table_name, c.relrowsecurity, c.relforcerowsecurity
|
||||
FROM pg_class c
|
||||
JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE n.nspname = 'atlas' AND c.relkind = 'r'
|
||||
ORDER BY c.relname;
|
||||
```
|
||||
|
||||
你应看到 `users`、`workspaces`、`stores`、`products`、`platform_listings` 等表;带 `workspace_id` 的业务表应显示 `rowsecurity = true` 和 `forcerowsecurity = true`。
|
||||
|
||||
## 6. 常见问题和重置边界
|
||||
|
||||
### 6.1 `role already exists` 或 `database already exists`
|
||||
|
||||
说明这台机器已经初始化过。不要再执行创建角色/数据库命令,直接检查并执行尚未应用的 migration。当前两个初始 SQL 不能安全地重复执行;以后由 migration runner 记录版本并避免重复。
|
||||
|
||||
### 6.2 `permission denied to create extension`
|
||||
|
||||
`001_foundation.sql` 需要 `citext`、`pgcrypto` 扩展。原生本机库可用管理员账号预先执行:
|
||||
|
||||
```sql
|
||||
CREATE EXTENSION IF NOT EXISTS citext;
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
```
|
||||
|
||||
然后再用迁移角色执行 migration。云托管数据库则由运维账户或云控制台预启用扩展。
|
||||
|
||||
### 6.3 要完全重建 Docker 开发库
|
||||
|
||||
下列命令会删除 Docker 命名卷中的**全部本地 Atlas 数据**,只用于你确定无需保留当前开发数据时:
|
||||
|
||||
```bash
|
||||
docker compose -f compose.postgres.local.yml down -v
|
||||
```
|
||||
|
||||
之后重新启动、创建角色/数据库并执行两份 migration。原生 PostgreSQL 不建议为重置而删除整个服务;只处理明确的 `atlas_dev` 数据库,并先确认其中没有需要保留的数据。
|
||||
|
||||
## 7. 与 staging / production 的差异
|
||||
|
||||
本地可以自行执行上述初始化;staging/production 不这样手工操作。云数据库管理员或基础设施脚本先创建角色和数据库,部署流程使用迁移角色执行 migration,随后 API/Worker 用应用角色启动。绝不把生产库当作家/公司开发库。
|
||||
|
||||
相关文档:[技术栈与部署](../architecture/technology-and-deployment.md)、[数据库角色与 RLS](../architecture/database-security.md)、[基础数据模型](../architecture/foundation-data-model.md)。
|
||||
Reference in New Issue
Block a user