lixi 7896a6d20b
CI / backend-test (push) Successful in 11m35s
feat(db): V6 creation schema 地基 + 补回 posts.generation_job_id 外键(T4-01)
creation schema 三表(generation_models / generation_styles /
generation_jobs)逐列取自已评审目标模型 patbond_postgresql.sql
(schema 60/68 + 对象 556-717),含 14 索引与 3 个 updated_at 触发器。

- 零外键需要裁剪:这是第一个不用砍跨 schema 外键的 baseline 迁移,
  identity/media(V1)与 pet_health(V3)目标 schema 均已存在
- 按 V5 文件头指令「M4 补回」补回 community.posts.generation_job_id
  → creation.generation_jobs(id) ON DELETE SET NULL;约束名显式写为
  posts_generation_job_id_fkey,即目标模型内联声明时 PostgreSQL 会
  自动生成的名字,使 V1..V6 建库与照目标模型建库约束名一致
- posts.region_id 保持裸列(区域系统属 M5,ADR-018 裁剪,M5 补回)
- 两条偏索引对齐 worker 语句:ix_generation_jobs_queue 的列序与出队
  ORDER BY 完全一致(SKIP LOCKED 领取走索引扫描不排序);
  ix_generation_jobs_running 驱动租约回收与待轮询扫描

两处刻意偏离目标模型,文件头已逐条记录原因:
1. input_asset_id 改可空(目标模型 NOT NULL)——参考图选填拍板;原
   NOT NULL 会使文生图无法表达。未发明 requires_input 标记(不超出
   已评审模型),「某模型是否必须有输入」由应用层查目录判断
2. 新增 first_attempt_at(目标模型无此列)——iteration-4/06 §4.2 指标
   口径要求。目标模型无法表达:其 ck_generation_jobs_state 强制
   queued 分支 started_at IS NULL,故重排队必然擦除首次尝试时间,
   导致排队时长与纯执行耗时永久无法分离。该列仅首次进入 running 时
   写入、不覆盖,并刻意排除在 queued 分支的置空要求之外;
   ck_generation_jobs_first_attempt 钉住其与 attempt_count 的不变式

测试:
- 新增 CreationMigrationIntegrationTest(12 项)。重点覆盖「日后同步回
  目标模型会静默改回去」的行为:复合外键真实拦截跨 media_kind 引用、
  queued→running→failed→queued 全程后 first_attempt_at 保留而
  started_at 被清空、不变式双向拒绝、provider_request_id 部分唯一索引
  (NULL 可共存、同值冲突)、idempotency_key 的按用户作用域
- CommunityMigrationIntegrationTest 原断言 %generation% 与 %region%
  外键合计为 0,V6 后必然变红(iteration-4/02 已预警)。拆为两条独立
  断言(generation=1、region=0)而非改成合计 1,使任一侧回退可单独定位

全量 381 → 393,零失败零跳过(auth 40 / common 3 / community 107 /
pet 100 / user 131→143)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 16:35:36 +08:00
2026-09-03 17:59:56 +08:00
2026-09-03 17:59:56 +08:00
2026-09-03 17:59:56 +08:00

Patbond API

Patbond API is a Spring Boot multi-module backend.

Modules

  • patbond-common: stable cross-module contracts (response wrapper, shared DTOs). No web/messaging dependencies.
  • patbond-user: user service for user creation, profile lookup, and password verification.
  • patbond-auth: authentication service for registration and login. Calls patbond-user over HTTP via OpenFeign with a configured static URL (no service discovery in the MVP, see ADR-002).
  • patbond-pet: pet profile and health record service (M2, ADR-009). Shares the database with patbond-user and only reads/writes the pet_health schema; the Flyway migration chain stays owned by patbond-user. First-wave skeleton: liveness endpoint only.

Technology Stack

  • Java 17 (build baseline; use JDK 17 for release builds)
  • Spring Boot 3.5.16
  • Spring Cloud 2025.0.3 (OpenFeign only)
  • PostgreSQL 18 + Flyway (patbond-user owns the migration chain: identity/media/platform/pet_health schemas)
  • Maven (use the committed Maven Wrapper ./mvnw)

Build and Test

./mvnw clean test

Integration tests start a disposable postgres:18 via Testcontainers, so a running Docker daemon is required (no local PostgreSQL installation or credentials are needed).

If your default JDK is not 17, point JAVA_HOME at a JDK 17 installation first, e.g.:

JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw clean test

Run

Running patbond-user requires a reachable PostgreSQL 18 database; Flyway applies the versioned migrations in patbond-user/src/main/resources/db/migration automatically on startup. Development seed data (db/dev, regions reference rows) is opt-in via a dev profile — see application.yml.sample.

Each service ships a committed application.yml.sample; the real application.yml is git-ignored. First copy the samples (defaults work locally, overrides via environment variables):

cp patbond-user/src/main/resources/application.yml.sample \
  patbond-user/src/main/resources/application.yml
cp patbond-auth/src/main/resources/application.yml.sample \
  patbond-auth/src/main/resources/application.yml
cp patbond-pet/src/main/resources/application.yml.sample \
  patbond-pet/src/main/resources/application.yml

Install the shared module once, then run each service in its own terminal:

./mvnw -pl patbond-common install
./mvnw -pl patbond-user spring-boot:run
./mvnw -pl patbond-auth spring-boot:run

Running the services also needs an RS256 key pair for access tokens (the private key signs in patbond-auth, the public key verifies in patbond-user; neither is committed):

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out jwt-private.pem
openssl pkey -in jwt-private.pem -pubout -out jwt-public.pem
export PATBOND_JWT_PRIVATE_KEY=$PWD/jwt-private.pem   # patbond-auth
export PATBOND_JWT_PUBLIC_KEY=$PWD/jwt-public.pem     # patbond-user

Tests do not require this copy step — patbond-auth/src/test/resources/application.yml keeps ./mvnw clean test self-contained on a clean checkout.

Smoke check:

curl -X POST http://127.0.0.1:8081/api/v1/auth/register \
  -H 'Content-Type: application/json' \
  -d '{"username":"demo_user","password":"secret123"}'

Configuration

Environment variable Default Used by
PATBOND_USER_PORT 8082 patbond-user
PATBOND_AUTH_PORT 8081 patbond-auth
PATBOND_USER_SERVICE_URL http://127.0.0.1:8082 patbond-auth (Feign target for patbond-user)
PATBOND_DB_URL jdbc:postgresql://127.0.0.1:5432/patbond patbond-user
PATBOND_DB_USER patbond patbond-user
PATBOND_DB_PASSWORD patbond patbond-user
PATBOND_INTERNAL_TOKEN dev-only-internal-token both (shared secret for /internal/**; inject a strong random value outside local dev)
PATBOND_JWT_PRIVATE_KEY (none, required) patbond-auth (RS256 private key: PEM path or inline PEM)
PATBOND_JWT_PUBLIC_KEY (none) patbond-user (RS256 public key: PEM path or inline PEM)
PATBOND_ACCESS_TTL 15m patbond-auth (access token lifetime, ADR-003)
PATBOND_REFRESH_TTL 30d patbond-user (refresh token lifetime, ADR-003)
PATBOND_LOGIN_LOCK_MAX_FAILURES 5 patbond-user (login failures before lockout)
PATBOND_LOGIN_LOCK_WINDOW 15m patbond-user (failure counting window)
PATBOND_LOGIN_LOCK_DURATION 15m patbond-user (lock duration)

Machine-specific values live in the git-ignored application.yml (copied from the committed .sample); never commit secrets to the samples.

Docker Compose

MVP 编排(ADR-007):postgres:18(数据落 volume)+ 两个无状态应用容器。 配置与本机运行同一套约定 —— 容器内挂载 application.yml.sample 作为配置, PATBOND_* 环境变量注入实际值。

# 1. 生成 RS256 密钥对与 .env(DB 口令、内部令牌;产物被 .gitignore 忽略)
./deploy/init-secrets.sh

# 2. 构建可执行 jar
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw -DskipTests package

# 3. 启动(首次会构建镜像)
docker compose up -d --build

# 冒烟
curl -s -X POST http://127.0.0.1:8081/api/v1/auth/register \
  -H 'Content-Type: application/json' \
  -d '{"username":"demo_user","password":"secret123"}'

# 停止(-v 同时删除数据库数据)
docker compose down

注意:数据库端口不对宿主机发布;8082(user)在 MVP 阶段直连暴露以提供 /api/v1/me,/internal/** 由服务间令牌保护,规模化阶段应改由网关统一入口。

Services

Auth Service

  • Application name: patbond-auth
  • Default port: 8081
  • Register: POST /api/v1/auth/register
  • Login: POST /api/v1/auth/login
  • Refresh (rotates the refresh token): POST /api/v1/auth/refresh
  • Logout (revokes the current session): POST /api/v1/auth/logout

User Service

  • Application name: patbond-user
  • Default port: 8082
  • Current user profile: GET /api/v1/me (Bearer access token, verified locally with the RS256 public key)
  • Internal (require X-Internal-Token):
    • Create user: POST /internal/users
    • Verify password: POST /internal/users/verify-password
    • Get user by id: GET /internal/users/{id}
    • Get user by username: GET /internal/users/by-username/{username}
    • Sessions: POST /internal/sessions, POST /internal/sessions/refresh, POST /internal/sessions/revoke

Note: user data is persisted in PostgreSQL (identity.users / identity.user_credentials, bcrypt password hashes, UUIDv7 ids generated in the application). Refresh sessions live in identity.auth_sessions (SHA-256 digests only, rotation on every refresh, token-family revocation on reuse, ADR-003). Errors follow the {code, message, data} envelope with stable business codes and matching HTTP statuses; the public contract is documented in patbond-doc/docs/api/openapi.yaml.

S
Description
Patbond api
Readme 1.1 MiB
Languages
Java 98.3%
PLpgSQL 1.1%
Shell 0.5%
Dockerfile 0.1%