Files
past-go/past-go
Blizzard 4934a69581 feat: migrate to PostgreSQL GORM and MinIO file storage
Use UUID models and soft deletion, persist file relationships with deduplicated monthly MinIO objects, and add admin file management. Update mini-program media handling and add local dev configuration.
2026-11-01 11:48:45 +08:00
..
2026-10-07 09:28:43 +08:00

本地 dev 调试

在 past-go/ 目录执行 make dev,使用 config.dev.yaml 启动,监听 :8080。 本地配置包含 AppSecret,已加入 Git 忽略;新环境可复制 config.dev.example.yaml 后填写。 管理端地址:http://localhost:8080/backend/admin/。

小程序 weapp/config/env.js 根据微信运行版本选择地址:开发版为 http://127.0.0.1:8080/api,体验版/正式版为线上地址。 开发工具需关闭合法域名校验(项目已设置);真机调试将 dev 地址改为电脑的局域网 IP。 小程序 AppID 为 wxb463820bf36dd5d6,AppSecret 仅存放在后端本地配置。 dev 继续使用当前 past-go 数据库与 MinIO bucket,并非独立测试数据。 日志和缓存写入 run/dev/data/,定时补发通知关闭。 新小程序的订阅模板和虚拟支付参数需在对应平台获取后填写,dev 配置暂时留空。

PostgreSQL / GORM 数据层(当前版本)

数据库为 192.168.100.127:5432/past-go,账号 root;连接配置见 config.example.yaml。 复制为本地 config.yaml 并填写微信配置。启动时先连接 postgres 管理库检查并创建目标库,再执行 GORM AutoMigrate。 也可以只运行迁移:

go run ./cmd/migrate -config config.yaml

模型定义在 internal/model/models.go,所有模型嵌入 BaseModel:Id string、CreatedAt、UpdatedAt、DeletedAt。 主键与关联 ID 使用 PostgreSQL uuid 类型;主键默认 gen_random_uuid(),不自增。 表名统一为 past_ + 单数 snake_case(例如 User → past_user、DiaryComment → past_diary_comment);字段按 UserId → user_id 命名。 启动时会将旧 past- 前缀的表无损重命名为 past_,保留 UUID、数据和索引。

新增、修改、删除通过 GORM 模型执行;复杂报表通过 GORM Raw 执行,并统一过滤所有关联表及子查询中的 deleted_at。 删除保留原行及附件;活跃记录的唯一索引带 WHERE deleted_at IS NULL,删除后可以重新创建同业务键的记录。 用户对外的六位展示编号是独立业务字段,继续使用数据库序列分配;它不是 UUID 主键。情侣空间编号为 CP + 不带连字符的情侣 UUID。

本次创建的是新的 PostgreSQL 库,不会导入旧 MySQL 数据。历史备份如需迁入,需要单独完成数据转换和旧 ID 到 UUID 的关联映射。

验证:go test ./...、go vet ./...;在设置 PAST_TEST_POSTGRES_DSN 后,可运行 go test ./internal/store -run TestPostgresIntegration -v 验证真实 PostgreSQL(测试数据全部回滚)。

MinIO 配置读取 minio 节点。past_files 保存 UUID、size、suffix、content_type、object_key、md5、sha256、url、bucket 等元数据;对象键格式为 YYYY/MM/<md5>.<suffix>。同内容并发上传通过 PostgreSQL 锁和唯一索引去重,不重复上传对象。

头像、相册封面、照片、打卡图片和背景使用带外键的文件 ID 字段;日记/记忆日图片使用 past_record_file 按 position 保存多图顺序。旧接口的路径字段在数据库中仅保存 ID 镜像,响应时解析为稳定文件访问地址;新请求可直接提交 fileId、avatarFileId、coverFileId、imageFileIds 等字段。past_file_upload 独立记录每个上传用户,既保留共享去重,也保证权限与容量统计正确。

上传成功返回 fileId、path、url、file、duplicate。/api/files/:fileId 生成短期 MinIO 签名链接,维持原图片的可访问方式,不修改 bucket 的公共访问策略。删除业务关联只做逻辑删除,不删除共享对象。容量检查在内容去重后于事务内执行,同文件不会重复占用同一空间的额度。

后台 文件管理(/backend/admin/files)通过管理员接口 /api/adminFiles 查看全部文件、上传用户、预览、对象路径及元数据,支持关键词、类型、日期筛选和分页。普通用户不能调用文件管理接口。

真实 PostgreSQL/MinIO 回归:设置 PAST_TEST_POSTGRES_DSN 后运行 go test ./...。测试读取本地 config.yaml 中的 MinIO 连接,可通过 PAST_TEST_CONFIG 指定其他配置文件的绝对路径,使用隔离的 schema/bucket,测试结束清理;微信/地图外部请求使用模拟依赖。

以下保留历史说明,数据库配置与部署步骤以以上内容为准。


我们past后端(Go 版)

backend/api/index.php 的 Go 重写:Gin + GORM + PostgreSQL,管理后台(React)通过 embed 打进同一个二进制。 小程序与管理后台统一使用驼峰 /api/接口名(如 /api/periodList),请求参数与响应字段同样使用驼峰。业务值与鉴权延续现有约定。旧 PHP 风格入口继续兼容已发布客户端。

本次 Nginx 与部署说明见 deploy/README.md。

目录

cmd/server/          入口
internal/config/     config.yaml 读取
internal/store/      GORM 数据访问、PostgreSQL 自动迁移与逻辑删除
internal/httpapi/    gin 路由、Ctx($_GET/$_POST/$input/$_FILES 等价物)、鉴权
internal/handler/    121 个 action,按模块拆分(user/couple/timeline/album/diary/songs/checkin/period/pet/misc/admin)
internal/upload/     MinIO 上传、MD5 去重、日期目录与签名访问
internal/wechat/     access_token / 内容安全 / 订阅消息 / jscode2session
internal/geo/        腾讯位置服务
internal/notify/     cron_notify.php 的进程内定时版
web/admin/           React 管理后台(dist/ 被 embed)

运行

cp config.example.yaml config.yaml   # 填写数据库 / 微信配置
go build -o past-server ./cmd/server
./past-server -config config.yaml

运行目录布局(data_dir,默认为二进制所在目录):

data_dir/
├── uploads/   用户上传文件(从 PHP 的 backend/uploads 原样迁移)
└── data/      access_token.cache 等

URL 映射:

URL 说明
/api/接口名 小程序、管理后台、上传统一入口
/backend/api/index.php?action=… 旧客户端兼容入口
/backend/uploads/… 上传文件静态服务(对应 DB 中 uploads/… 路径)
/backend/admin/ 管理后台 SPA
/healthz 健康检查

与 PHP 版的差异(有意为之)

见 ../dev/API_CONTRACT.md 与对比测试 ../dev/apidiff.py。已知的 PHP 原有 bug 处理:

  • *_add 接口返回的 id 改为真实记录 id(PHP 返回的是 activity_log 的 id)。
  • pet_dynamics 分页可用(PHP 因 LIMIT '20' 语法错误恒 500)。
  • 宠物成就解锁可用(PHP 因 pet_dynamics.user_id=0 违反外键恒 500)。
  • logout 会真正失效 token;登录不再踢掉其它设备的 token。
  • 数据库迁移只在启动时执行一次(PHP 每次请求执行)。