ab0265c17b
- 新增 docker-compose.yml、两服务 Dockerfile(eclipse-temurin:17-jre、非 root、仅装 exec jar)与 deploy/init-secrets.sh(幂等生成 RS256 密钥对与 .env 随机机密,产物入 .gitignore) - 容器配置复用 application.yml.sample(SPRING_CONFIG_LOCATION 挂载)+ PATBOND_* 环境变量,与本机运行同一套约定;DB 端口不对宿主机发布 - spring-boot-maven-plugin 显式绑定 repackage(本工程无 starter-parent,此前 package 产物不可执行),exec classifier 保留普通 jar 供 auth 模块 E2E 依赖 - 验收:docker compose up -d --build 后完整冒烟通过(register→me→refresh→旧 token 重用 40102→/internal 无凭证 401→logout),down -v 无残留;./mvnw clean test 全绿 74 测试 - README 新增 Docker Compose 一节 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
164 lines
6.4 KiB
Markdown
164 lines
6.4 KiB
Markdown
# 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).
|
||
|
||
## 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 `identity`/`media` schemas)
|
||
- Maven (use the committed Maven Wrapper `./mvnw`)
|
||
|
||
## Build and Test
|
||
|
||
```bash
|
||
./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.:
|
||
|
||
```bash
|
||
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):
|
||
|
||
```bash
|
||
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
|
||
```
|
||
|
||
Install the shared module once, then run each service in its own terminal:
|
||
|
||
```bash
|
||
./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):
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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_*` 环境变量注入实际值。
|
||
|
||
```bash
|
||
# 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`.
|