diff --git a/README.md b/README.md
new file mode 100644
index 0000000..975e07c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,37 @@
+# Java 21 Service Template
+
+A production-ready Java 21 service template with Spring Boot 3.4+, Clean Architecture, multi-tenancy, and comprehensive documentation.
+
+## Documentation
+
+Complete project documentation is available in the [`docs/`](docs/README.md) directory, covering architecture, API specifications, security, database, deployment, observability, operations manuals, release processes, development guidelines, configuration, testing, and architectural decision records.
+
+| Core Documentation | Description |
+|----------|------|
+| [Architecture Design](docs/architecture.md) | Layered architecture, ArchUnit rules, multi-tenancy |
+| [API Guide](docs/api-guide.md) | Endpoints, request/response formats, error codes |
+| [Security Design](docs/security.md) | Authentication, authorization, tenant trust boundaries, compliance |
+| [Database](docs/database.md) | Schema, indexing strategies, Flyway migration specs |
+| [Observability](docs/observability.md) | Metrics, logging, tracing, alerting, SLO/SLA |
+| [Operations Manual](docs/runbook.md) | Troubleshooting guides |
+| [Deployment](docs/deployment.md) | Docker, Kubernetes, Kustomize |
+| [Release Process](docs/release.md) | Versioning, release flows, rollback strategies |
+| [Development Guide](docs/development.md) | Local environment, IDE, debugging |
+| [Configuration Reference](docs/configuration.md) | Profiles, properties, environment variables |
+| [Testing](docs/testing.md) | Test layers, Testcontainers, ArchUnit rules, quality gates |
+| [ADR](docs/adr/) | Architectural Decision Records |
+| [Vulnerability Disclosure](SECURITY.md) | Security vulnerability reporting channels and response SLA |
+
+## Quick Start
+
+For detailed setup instructions, see [development.md](docs/development.md).
+
+## Features
+
+- Java 21 with Virtual Threads
+- Spring Boot 3.4+ with Clean Architecture (Hexagonal/Ports & Adapters)
+- Multi-tenant SaaS architecture with strict tenant isolation
+- Comprehensive documentation set
+- ArchUnit-enforced architecture rules
+- Production-ready observability and security
+- Docker and Kubernetes deployment ready
\ No newline at end of file
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..a2fee70
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,46 @@
+# Security Policy
+
+## Supported Versions
+
+| Version | Supported |
+|---------|--------------------|
+| 1.x | :white_check_mark: |
+| < 1.0 | :x: |
+
+## Reporting a Vulnerability
+
+If you discover a security vulnerability in this project, please report it
+responsibly.
+
+**Do NOT open a public GitHub issue.**
+
+Send an email to **security@ljwx.com** with the following information:
+
+- Description of the vulnerability and its potential impact.
+- Steps to reproduce the issue, including any proof-of-concept code.
+- Affected version(s).
+- Any suggestions for a fix (optional but appreciated).
+
+### Response SLA
+
+| Stage | Target Time |
+|----------------------|-------------|
+| Acknowledgement | 2 business days |
+| Initial assessment | 5 business days |
+| Fix or mitigation | 14 calendar days (critical), 30 calendar days (medium/low) |
+| Public disclosure | After fix is released and users have had reasonable time to upgrade |
+
+We follow the principle of **coordinated vulnerability disclosure**. We will
+credit reporters unless they wish to remain anonymous.
+
+### Scope
+
+This policy covers the `java-service-template` repository and its published
+Docker images. Third-party dependencies are monitored via OWASP
+Dependency-Check and Dependabot; please report upstream vulnerabilities
+directly to the respective maintainers.
+
+### Security Updates
+
+Security patches are released as point versions (e.g., 1.0.1) and announced
+via the CHANGELOG.md and GitHub Releases.
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..7f19463
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,47 @@
+# java-service-template 文档索引
+
+本目录包含项目的全部生产级文档。建议按以下顺序阅读。
+
+## 核心文档
+
+| 文档 | 说明 |
+|------|------|
+| [architecture.md](architecture.md) | 分层架构、ArchUnit 规则、多租户设计 |
+| [api-guide.md](api-guide.md) | REST API 规范、请求/响应格式、错误码 |
+| [database.md](database.md) | 数据库 Schema、索引策略、Flyway 迁移规范 |
+| [configuration.md](configuration.md) | 配置属性、Profile 矩阵、环境变量 |
+| [security.md](security.md) | 认证授权、租户信任边界、Actuator 暴露策略、漏洞管理 |
+
+## 开发与测试
+
+| 文档 | 说明 |
+|------|------|
+| [development.md](development.md) | 本地开发环境搭建、IDE 配置、调试技巧 |
+| [testing.md](testing.md) | 测试分层、Testcontainers、ArchUnit 规则、质量门 |
+
+## 运维与发布
+
+| 文档 | 说明 |
+|------|------|
+| [deployment.md](deployment.md) | Docker 构建、Kubernetes/Kustomize 部署、探针配置 |
+| [observability.md](observability.md) | 指标、日志、追踪、告警策略、SLO/SLA |
+| [runbook.md](runbook.md) | 常见故障排查手册 |
+| [release.md](release.md) | 版本号规范、发布流程、回滚策略 |
+
+## 架构决策记录 (ADR)
+
+| ADR | 标题 |
+|-----|------|
+| [0001](adr/0001-layered-architecture.md) | 采用分层 + 六边形架构 |
+| [0002](adr/0002-multi-tenancy-via-header.md) | HTTP Header 多租户方案 |
+| [0003](adr/0003-optional-redis-cache.md) | 可选 Redis 缓存(属性开关) |
+| [0004](adr/0004-dto-as-java-record.md) | DTO 使用 Java Record |
+| [0005](adr/0005-cache-error-handler.md) | 缓存异常容错策略 |
+
+## 其他
+
+| 文件 | 位置 | 说明 |
+|------|------|------|
+| [SECURITY.md](../SECURITY.md) | 仓库根目录 | 漏洞披露政策与响应 SLA |
+| [CONTRIBUTING.md](../CONTRIBUTING.md) | 仓库根目录 | 贡献指南 |
+| [CHANGELOG.md](../CHANGELOG.md) | 仓库根目录 | 版本变更记录 |
\ No newline at end of file
diff --git a/docs/adr/0005-cache-error-handler.md b/docs/adr/0005-cache-error-handler.md
new file mode 100644
index 0000000..ce2ab61
--- /dev/null
+++ b/docs/adr/0005-cache-error-handler.md
@@ -0,0 +1,49 @@
+# ADR-0005: 缓存异常容错策略
+
+**状态**: 已采纳
+**日期**: 2026-02-12
+**决策者**: 模板维护团队
+
+## 上下文
+
+tmpl-api 通过 `@Cacheable` / `@CacheEvict` 使用 Redis 缓存。当 Redis 不可用时,
+Spring 默认的 `SimpleCacheErrorHandler` 会将异常直接抛出给调用方,导致本应正常的
+数据库查询也返回 500 错误。这对服务可用性造成不可接受的影响。
+
+## 决策驱动因素
+
+- 缓存是可选的性能优化,不应成为服务可用性的瓶颈。
+- 运维团队需要在日志中看到缓存异常的完整堆栈以便排查。
+- `GracefulCacheErrorHandler` 应与 `app.cache.enabled` 属性开关联动。
+- ArchUnit A-3 规则要求 Application 层不直接依赖 Infrastructure 实现。
+
+## 考虑的方案
+
+**方案 A — 自定义 CacheErrorHandler(已采纳)**
+实现 `CachingConfigurer` 接口,注册 `GracefulCacheErrorHandler`,在 `handleCacheGetError` /
+`handleCachePutError` / `handleCacheEvictError` / `handleCacheClearError` 中记录 WARN
+日志(含完整堆栈)并吞掉异常。
+
+**方案 B — Resilience4j CircuitBreaker 包裹缓存调用**
+为 Redis 调用添加断路器,在失败率达到阈值时自动打开。
+
+**方案 C — 不处理,缓存失败即服务失败**
+保持默认 `SimpleCacheErrorHandler`。
+
+## 决策
+
+采纳方案 A。方案 B 增加了额外依赖和复杂度,可在后续迭代中作为增强引入(可与方案 A 共存)。
+方案 C 不可接受,因为它让可选组件的故障影响核心功能。
+
+## 后果
+
+**正面**:Redis 不可用时服务自动降级为直接查询数据库,对终端用户透明;运维可通过 WARN 日志和
+`redisPing` HealthIndicator 感知异常并修复。
+
+**负面**:降级期间数据库负载会上升;若数据库也接近瓶颈则服务仍可能受影响。
+
+## 后续行动
+
+- 在 Grafana Dashboard 中为 `cache_gets_total{result="miss"}` 创建告警。
+- 评估 Resilience4j CircuitBreaker 作为方案 B 的补充。
+- 考虑本地 Caffeine 二级缓存作为 Redis 不可用时的过渡。
\ No newline at end of file
diff --git a/docs/api-guide.md b/docs/api-guide.md
new file mode 100644
index 0000000..466c38e
--- /dev/null
+++ b/docs/api-guide.md
@@ -0,0 +1,192 @@
+# API 指南 (API Guide)
+
+本文档定义 tmpl-api 服务的 REST API 规范,包括端点、请求/响应格式、错误码、认证授权等。
+
+---
+
+## 1. API 端点规范
+
+### 1.1 命名约定
+
+| 资源 | 列表 | 详情 | 创建 | 更新 | 删除 |
+|------|------|------|------|------|------|
+| 订单 | `GET /api/v1/orders` | `GET /api/v1/orders/{id}` | `POST /api/v1/orders` | `PATCH /api/v1/orders/{id}/status` | `DELETE /api/v1/orders/{id}` |
+
+### 1.2 版本控制
+
+API 版本通过 URL 路径控制:`/api/v{version}/{resource}`。当前版本为 `v1`。
+
+### 1.3 HTTP 方法语义
+
+- `GET`:安全、幂等,用于查询资源
+- `POST`:非幂等,用于创建资源或触发操作
+- `PUT`:幂等,用于完全替换资源(当前未使用,使用 PATCH)
+- `PATCH`:幂等,用于部分更新资源
+- `DELETE`:幂等,用于删除资源
+
+---
+
+## 2. 请求格式
+
+### 2.1 内容类型
+
+| 方法 | Content-Type | Accept |
+|------|--------------|--------|
+| GET | (无) | `application/json` |
+| POST | `application/json;charset=UTF-8` | `application/json` |
+| PATCH | `application/json;charset=UTF-8` | `application/json` |
+| DELETE | (无) | `application/json` |
+
+### 2.2 多租户 Header(必需)
+
+所有请求必须携带以下 Header:
+
+| Header | 类型 | 说明 |
+|--------|------|------|
+| `X-Tenant-Id` | String | 租户标识,长度 ≤ 64 字符 |
+| `X-Dept-Id` | String | 部门标识,长度 ≤ 64 字符,"0" 表示跨部门 |
+| `X-User-Id` | Long | 操作人 ID,必须为合法数字 |
+
+> **重要**:这些 Header 由 API Gateway 从 JWT Token 中提取并注入,服务不信任客户端直接传入的值。
+
+---
+
+## 3. 响应格式
+
+### 3.1 成功响应
+
+```json
+{
+ "code": 200,
+ "message": "Success",
+ "data": {
+ // 具体业务数据
+ },
+ "timestamp": "2026-02-12T10:00:00.123Z",
+ "traceId": "abc123def456"
+}
+```
+
+### 3.2 分页响应
+
+```json
+{
+ "code": 200,
+ "message": "Success",
+ "data": {
+ "items": [...],
+ "total": 100,
+ "pageNum": 1,
+ "pageSize": 20,
+ "pages": 5
+ },
+ "timestamp": "2026-02-12T10:00:00.123Z",
+ "traceId": "abc123def456"
+}
+```
+
+### 3.3 创建响应
+
+```json
+{
+ "code": 201,
+ "message": "Created",
+ "data": {
+ // 新创建的资源数据
+ },
+ "timestamp": "2026-02-12T10:00:00.123Z",
+ "traceId": "abc123def456"
+}
+```
+
+### 3.4 空响应
+
+DELETE 操作成功时返回 204 No Content,无响应体。
+
+---
+
+## 4. 错误响应
+
+### 4.1 标准错误格式
+
+```json
+{
+ "code": 400,
+ "errorCode": "COMMON_INVALID_PARAM",
+ "message": "参数校验失败",
+ "details": "orderNo 不能为空", // 仅非生产环境返回
+ "timestamp": "2026-02-12T10:00:00.123Z",
+ "traceId": "abc123def456"
+}
+```
+
+### 4.2 通用错误码
+
+| HTTP 状态码 | errorCode | message | 说明 |
+|-------------|-----------|---------|------|
+| 400 | `COMMON_INVALID_PARAM` | 参数校验失败 | 请求参数不符合约束 |
+| 400 | `AUTH_MISSING_TENANT` | 缺少租户标识 | 缺少 X-Tenant-Id Header |
+| 400 | `AUTH_MISSING_DEPT` | 缺少部门标识 | 缺少 X-Dept-Id Header |
+| 400 | `AUTH_INVALID_USER_ID` | 用户标识格式不合法 | X-User-Id 非合法数字 |
+| 400 | `AUTH_HEADER_CONFLICT` | 请求头信息冲突 | 新旧 Header 值不一致 |
+| 403 | `AUTH_ACCESS_DENIED` | 无权限 | 认证失败或权限不足 |
+| 403 | `AUTH_FORBIDDEN_SCOPE` | 部门范围越权 | deptId=0 但无 SCOPE_ALL_DEPT 权限 |
+| 404 | `COMMON_NOT_FOUND` | 资源不存在 | 请求的资源不存在 |
+| 409 | `COMMON_OPTIMISTIC_LOCK` | 数据已被修改,请刷新重试 | 乐观锁冲突 |
+| 422 | `ORDER_AMOUNT_INVALID` | 订单金额不合法 | 订单金额相关业务校验失败 |
+| 500 | `COMMON_INTERNAL_ERROR` | 系统内部错误 | 服务器内部错误 |
+
+---
+
+## 5. 认证
+
+### 当前状态
+
+模板当前未启用认证(`SecurityConfig` 配置为 `permitAll()`),适用于开发和演示环境。
+
+### 生产要求
+
+在生产部署中,所有 `/api/**` 请求必须经过认证。推荐方案为 JWT Bearer Token
+(`spring-boot-starter-oauth2-resource-server`)。
+
+### X-Tenant-Id Header
+
+所有 API 请求必须携带 `X-Tenant-Id` Header。
+
+**重要**:在生产环境中,此 Header 的值必须由 API Gateway / 认证层从已验证的
+JWT Token 中提取并注入,不得信任客户端直接传入的值。tmpl-api 假设上游已完成校验。
+
+缺失该 Header 时,服务返回 `400 Bad Request`:
+
+```json
+{
+ "code": 400,
+ "message": "Missing X-Tenant-Id header",
+ "data": null
+}
+```
+
+---
+
+## 6. 限流与熔断
+
+当前未实现限流与熔断机制,计划在后续迭代中引入 Resilience4j。
+
+---
+
+## 7. 监控指标
+
+API 调用通过 Micrometer 自动记录以下指标:
+
+- `http.server.requests`:请求计数、延迟、状态码分布
+- `http.client.requests`:下游服务调用指标(如有)
+- 业务自定义指标(如 `order.created.total`)
+
+---
+
+## 8. 安全考虑
+
+- 输入参数必须经过校验,防止注入攻击
+- 敏感信息不在日志中记录
+- 所有查询自动附加租户隔离条件
+- 响应中不泄露系统内部信息
\ No newline at end of file
diff --git a/docs/architecture.md b/docs/architecture.md
new file mode 100644
index 0000000..b7244ed
--- /dev/null
+++ b/docs/architecture.md
@@ -0,0 +1,829 @@
+# M2 整体架构(终稿 v5——实施基准·定版)
+
+## 一、定位与目标
+
+M2 是一个可编译、可测试、可部署、具备自动门禁的 Java 21 通用项目模板。它不是业务系统,而是所有新服务的起点。任何团队 fork 后运行 `init.sh --service=xxx`,30 分钟内应能在 dev 环境跑通完整的"建表 → 启动 → CRUD → CI 绿灯 → 镜像推送 → ArgoCD 部署"链路。
+
+M2 包含一个贯穿四层的示例 bounded context(Order),用于验证模板自身的正确性。通过 `init.sh --sample=none` 可清除示例代码,仅保留基础设施骨架,清除后仍可编译、启动、通过 CI、构建镜像。
+
+## 二、技术栈基线(版本锁定规则)
+
+**版本冻结规则**:`spring-boot.version` 在父 POM 中显式锁定,必须是已发布 GA 版本。模板仓库以 POM 中的实际值为唯一版本真相源,文档不声称"当前最新"。团队升级版本时必须在本地全量跑通 CI quality-gate 后方可提交版本变更 PR。
+
+| 组件 | 版本(以 POM 锁定值为准) | 说明 |
+|---|---|---|
+| JDK | 21(Eclipse Temurin) | LTS,Virtual Threads 默认开启 |
+| Spring Boot | 父 POM 锁定 GA 版本 | 升级需全量验证 |
+| Maven | 3.9+ | 多模块构建 |
+| PostgreSQL | 15+ | 唯一基线数据库 |
+| Flyway | Spring Boot 管理版本 | 数据库迁移 |
+| Hibernate | 6.x(Spring Boot 管理) | JPA 实现 |
+| MapStruct | 1.6.x | 对象映射,编译期生成 |
+| SpringDoc OpenAPI | 2.8+ | API 文档 |
+| Redis | 7+ | 默认就绪,Spring Cache 抽象 |
+| Logback + logstash-logback-encoder | 父 POM 锁定 | 结构化 JSON 日志 |
+| Micrometer + Actuator | Spring Boot 管理 | 指标 + 健康检查 |
+| ArchUnit | 1.3+ | 架构守护测试 |
+| Spotless + Google Java Format | Maven 插件 | 代码格式化 |
+| SpotBugs | Maven 插件 | 静态分析 |
+| OWASP Dependency Check | Maven 插件 | 依赖漏洞扫描 |
+| JaCoCo | Maven 插件 | 覆盖率门槛 ≥ 70% |
+| Testcontainers | 1.20+ | 集成测试(PostgreSQL/Redis) |
+| git-commit-id-maven-plugin | 父 POM 锁定 | 构建信息写入 actuator/info |
+
+## 三、仓库拓扑(双仓库 GitOps)
+
+```
+┌──────────────────────────────────────┐ ┌──────────────────────────────────┐
+│ java-service-template │ │ java-service-template-deploy │
+│ (应用仓库) │ │ (部署仓库) │
+│ │ │ │
+│ 源码 + Gitea CI │ │ Kustomize 清单 │
+│ quality-gate: 测试/格式/质量 │ │ base/ + overlays/dev|staging|prod│
+│ build-and-deploy: │─────▶│ │
+│ 构建镜像 → 推送 Harbor │ │ CI 更新镜像标签 │
+│ 更新部署仓库镜像标签 │ │ ArgoCD 监听 → 自动同步 K8s │
+└──────────────────────────────────────┘ └──────────────────────────────────┘
+```
+
+两个仓库通过镜像标签(git short SHA,7 位)建立唯一关联。应用仓库的 CI build-and-deploy Job 通过幂等脚本 clone 部署仓库,用 `yq` 精确替换对应 service + env 的镜像标签后 commit push。部署仓库无 CI workflow,仅由 ArgoCD 监听,不存在循环触发。
+
+## 四、四层模块架构与依赖方向
+
+```
+ 依赖方向:外 → 内,严格单向
+
+ ┌──────────────────────────────────────────────────────────┐
+ │ tmpl-api │
+ │ Spring Boot 启动类 / Controller / Filter / DTO │
+ │ ExceptionHandler / Config / Flyway 迁移脚本 / 配置文件 │
+ │ ArchUnit 测试 / 集成测试 │
+ ├──────────────────────────┬───────────────────────────────┤
+ │ depends on │ depends on │
+ ▼ ▼ │
+ ┌──────────────────┐ ┌───────────────────────────────┐ │
+ │ tmpl- │ │ tmpl-application │ │
+ │ infrastructure │ │ │ │
+ │ │ │ UseCase / Port(in+out) │ │
+ │ JPA PO / Dao │ │ Command / Query / DTO │ │
+ │ Adapter │─▶│ TenantContext / TenantScope │ │
+ │ 审计 / 事件发布 │ │ DomainEventPublisher(接口) │ │
+ │ 外部服务适配 │ │ @Transactional 唯一合法位置 │ │
+ └──────────────────┘ └───────────────┬───────────────┘ │
+ │ depends on │
+ ┌──────────────▼──────────────┐ │
+ │ tmpl-domain │ │
+ │ │ │
+ │ Entity / ValueObject / Enum │ │
+ │ Repository(接口) │ │
+ │ DomainException / ErrorCode │ │
+ │ DomainEvent │ │
+ │ │ │
+ │ 纯 Java,零框架依赖 │ │
+ └──────────────────────────────┘ │
+ ────────────────────────────────────────────────────────────┘
+```
+
+### 4.1 各层依赖铁律(ArchUnit 强制执行)
+
+**domain 层**:不依赖任何上层模块。禁止出现 Spring、JPA、Hibernate、Jakarta Persistence 的 import。允许 Java 标准库 + Jakarta Validation 注解(仅用于 ValueObject/Entity 的构造与不可变约束表达,禁止注入 `jakarta.validation.Validator` 或任何运行时校验器,业务错误仍通过 `DomainException(ErrorCode)` 抛出)。
+
+**application 层**:依赖 domain + Spring Context/TX(用于 DI 与事务声明)。允许 ` @Transactional`、`@Service`、`@Component`。禁止依赖 Web(` @RestController`、`@RequestMapping`)、Persistence(`@Entity`、`JpaRepository`)、Cache(`@Cacheable`)、MQ(`KafkaTemplate`)、Security(`@PreAuthorize`)、HTTP Client(`RestTemplate`、`WebClient`、` @FeignClient`)。
+
+**infrastructure 层**:依赖 application + domain。实现 application 定义的 Port 接口。不声明 ` @Transactional`(事务由 application 层控制)。
+
+**api 层**:依赖 infrastructure + application + domain。是唯一的 Spring Boot 启动入口。Controller 禁止直接依赖 infrastructure 中的具体实现类,只通过 application 层的 UseCase 接口交互。禁止字段注入(` @Autowired` on field),必须使用构造器注入。
+
+## 五、多租户隔离模型
+
+### 5.1 字段命名与类型(冻结)
+
+模板内部统一使用 `tenantId` 和 `deptId`。`customerId` / `orgId` 仅出现在 TenantContextFilter 的兼容解析逻辑中,核心域、应用层、端口、数据库查询一律不出现旧名。
+
+| 字段 | Java 类型 | 必填 | 物理列名 | PG 列类型 | 说明 |
+|---|---|---|---|---|---|
+| tenantId | String | 是 | tenant_id | varchar(64) | 租户标识(原 customerId) |
+| deptId | String | 是 | dept_id | varchar(64) | 部门标识(原 orgId),"0" 表示跨部门 |
+| userId | Long | 是 | — | — | 当前操作人 ID(与审计列 bigint 对齐) |
+| roles | Set\ | 是 | — | — | 角色集合 |
+
+**userId 类型决策说明**:userId 统一为 Long,与公司历史表规范(`create_user_id bigint`)保持一致。TenantContextFilter 解析 `X-User-Id` 时若非合法数字,直接返回 400,errorCode 为 `AUTH_INVALID_USER_ID`。系统无上下文场景(批处理/异步)使用约定系统账号 ID(`0L`)。
+
+**deptId 存储规则(冻结)**:`dept_id` 列永远存储真实部门 ID,不允许写入 `'0'`。`"0"` 仅存在于 TenantScope 请求上下文中,用于控制"是否追加 dept_id 过滤条件"的查询逻辑。这样避免数据层出现"跨部门虚拟 dept"的脏值。
+
+可选扩展字段(不进 M2 基线,预留位置):requestId、clientId/appId。
+
+### 5.2 Header 契约(冻结)
+
+| 标准 Header | 旧 Header(兼容期) | 说明 |
+|---|---|---|
+| X-Tenant-Id | X-Customer-Id | 由网关注入 |
+| X-Dept-Id | X-Org-Id | 由网关注入 |
+| X-User-Id | — | 由网关注入,必须为合法数字 |
+| X-User-Roles | — | 逗号分隔 |
+
+### 5.3 兼容窗口与失败策略(冻结)
+
+兼容行为由配置项 `app.security.header-compat` 控制,代码默认值为 `false`。
+
+**配置出现规则**:仅 `application-dev.yml` 中设置 `app.security.header-compat: true`。`application-staging.yml` 中不出现(按需临时开启)。`application-prod.yml` 中不出现。
+
+**prod 硬禁保障(冻结)**:启动时校验——若 active profiles 包含 `prod` 且 `app.security.header-compat=true`(无论来自配置文件、环境变量还是启动参数),应用 **fail fast 拒绝启动**,抛出明确异常信息:`"header-compat=true is forbidden in prod profile"`。实现方式推荐 `ApplicationRunner` / `SmartLifecycle` / `InitializingBean`,确保能读取到 `spring.profiles.active` 与最终绑定的 `app.security.header-compat` 值。
+
+| 环境 | 配置文件中是否出现 | 生效值 | 行为 |
+|---|---|---|---|
+| dev | 是(true) | true | 缺新 Header 时回退旧 Header,输出 WARN + 标识 `LEGACY_HEADER_FALLBACK` |
+| staging | 默认不出现 | false(除非临时开启) | 同 prod 行为 |
+| prod | 不出现 | false(启动时强制校验,环境变量也无法覆盖) | 不回退 |
+
+**失败策略——Header 解析优先级(冻结)**:
+
+TenantContextFilter 按以下优先级顺序处理,命中即停:
+
+| 优先级 | 场景 | 结果 | HTTP | errorCode / 标识 |
+|---|---|---|---|---|
+| 1 | 新旧 Header 同时存在且值**不一致** | 拒绝 | 400 | AUTH_HEADER_CONFLICT |
+| 2 | 新旧 Header 同时存在且值**一致** | 允许通过,使用新 Header 值 | — | WARN 日志标识 `LEGACY_HEADER_REDUNDANT` + Micrometer counter `security.header.legacy_redundant` |
+| 3 | 仅有新 Header | 正常通过 | — | — |
+| 4 | 仅有旧 Header 且 header-compat=true | 回退使用旧 Header 值 | — | WARN 日志标识 `LEGACY_HEADER_FALLBACK` |
+| 5 | 仅有旧 Header 且 header-compat=false | 拒绝 | 400 | AUTH_LEGACY_HEADER_NOT_ALLOWED |
+| 6 | 新旧均无 X-Tenant-Id | 拒绝 | 400 | AUTH_MISSING_TENANT |
+| 7 | 新旧均无 X-Dept-Id | 拒绝 | 400 | AUTH_MISSING_DEPT |
+| 8 | X-User-Id 非合法数字 | 拒绝 | 400 | AUTH_INVALID_USER_ID |
+
+失败时不创建 TenantContext,不进入后续链路,直接返回统一错误格式的响应。
+
+### 5.4 deptId=0 的权限语义(冻结)
+
+deptId=0 表示"跨部门查询",是权限决策而非查询便利。网关在鉴权时判断用户角色,仅持有 `SCOPE_ALL_DEPT`(或等价 TENANT_ADMIN)权限的用户,网关才注入 `X-Dept-Id: 0`。普通用户的 X-Dept-Id 只能是其被授权的部门 ID。
+
+模板层面:TenantContextFilter 不做权限判断(权限是网关职责),但提供 scope 一致性校验。dev profile 下允许 deptId=0 通过但输出 WARN(标识 `DEPT_SCOPE_ALL_DEV_ONLY`)。staging/prod 下:若 deptId=0 且 roles 不含 `SCOPE_ALL_DEPT`,返回 403,errorCode `AUTH_FORBIDDEN_SCOPE`。
+
+**写入 contracts/security-context.md 的不可变条款**:服务内只做"范围一致性校验(scope validation)",不做权限决策(authorization decision)。权限决策由网关负责。
+
+### 5.5 数据隔离执行点
+
+**数据写入**:所有 Repository Port 的写方法(save / delete)显式接收 tenantId 参数,由 UseCase 从 TenantScope 传入。Adapter 持久化时将 tenantId 写入 PO。dept_id 始终写入真实部门 ID,不写入 "0"。
+
+**数据读取**:所有查询方法必须携带 tenantId 作为过滤条件。deptId 作为范围过滤,TenantScope 中 deptId 为 "0" 时 Adapter 不添加 dept_id 条件(不代表查询 dept_id='0' 的数据)。
+
+**Repository 安全——Dao 封装 + package-private 策略(冻结,方案 A)**:
+
+采用 internal 包隔离实现。OrderJpaRepository 为 package-private,与 OrderDao 置于同一个 `internal` 包中。OrderDao 为 public,仅暴露带 tenant 的方法。OrderPersistenceAdapter 在 `adapter` 包中,注入 public 的 OrderDao,编译期即无法访问 package-private 的 OrderJpaRepository。
+
+包结构冻结如下:
+
+```
+com.ljwx.tmpl.infrastructure.persistence.order.internal/
+ OrderJpaRepository.java ← package-private (无 public 修饰)
+ OrderDao.java ← public,仅暴露带 tenant 的方法
+
+com.ljwx.tmpl.infrastructure.persistence.order.adapter/
+ OrderPersistenceAdapter.java ← public,注入 OrderDao
+```
+
+ArchUnit 补充规则:`..adapter..` 包不得 import `..internal..*JpaRepository`(编译期已禁止,ArchUnit 作为二次防护)。`..adapter..` 包不得位于 `..internal..` 包内(防止绕过)。
+
+** @Query 租户检查规则(冻结——JPQL/Native SQL 分支)**:
+
+对 `..internal..` 包中所有 ` @Query` 注解:若 `nativeQuery = true`(原生 SQL),注解值必须包含 `tenant_id`(物理列名)。若 `nativeQuery = false` 或未指定(JPQL,默认),注解值必须包含 `tenantId`(实体字段名)或 `:tenantId`(命名参数)。
+
+**实现最佳实践**:所有 Repository 查询统一使用 `:tenantId` 命名参数(JPQL 和 native SQL 均可使用),ArchUnit 检查优先匹配 `:tenantId`,其次匹配字段名 `tenantId`(JPQL)或列名 `tenant_id`(native SQL),避免字段别名或字符串拼接导致误通过。
+
+**上下文传播与清理(冻结)**:同步请求通过 ThreadLocal(TenantContext)传播。TenantContextFilter 必须在 finally 块中精确清理本请求写入的 MDC key(不使用 `MDC.clear()`,避免清除其他 Filter 或框架写入的字段):
+
+```
+finally {
+ TenantContext.clear();
+ MDC.remove("traceId");
+ MDC.remove("tenantId");
+ MDC.remove("deptId");
+ MDC.remove("userId");
+}
+```
+
+这是防止"串租户"的安全底线,不可协商。任何修改 Filter 链的 PR 必须有对应的集成测试验证异常路径下上下文已清理。
+
+**DTO 信任边界**:Controller 层的 Request DTO 禁止包含 tenantId、deptId、userId 字段。这些字段的唯一可信来源是 TenantContextFilter 从网关 Header 解析。ArchUnit B 类规则扫描 Request DTO 类中是否出现这些字段名。
+
+### 租户 Header 信任边界
+
+`X-Tenant-Id` Header 在 tmpl-api 内部被视为已验证的值。**但服务本身不负责校验
+Header 的真实性**。在生产部署中,必须由 API Gateway 或认证层从已验证的
+JWT Token 中提取并注入,不得信任客户端直接传入的值。tmpl-api 假设上游已完成校验。
+
+信任链路:Client → API Gateway(校验 JWT → 提取 tenantId → 注入/覆盖 Header)→
+tmpl-api(读取 Header → 存入 TenantContext → 附加到所有查询条件)。
+
+详细安全设计参见 [security.md](security.md) 第 2 节。
+
+## 六、审计列实现(冻结)
+
+采用 **Spring Data JPA Auditing**,弃用自定义 EntityListener。
+
+### 6.1 启用方式
+
+配置类加 ` @EnableJpaAuditing(auditorAwareRef = "auditorAware")`。提供 `AuditorAwareImpl implements AuditorAware`,从 TenantContext 获取当前 userId(Long 类型)。批处理/异步/无上下文场景返回 `Optional.of(0L)`(约定系统账号 ID)。
+
+### 6.2 PO 审计字段(对齐公司表规范)
+
+| Java 字段 | Java 类型 | 注解 | 列映射 | 数据库列 | PG 类型 | 说明 |
+|---|---|---|---|---|---|---|
+| createUserId | Long | @CreatedBy | @Column(name="create_user_id") | create_user_id | bigint | 创建人 ID |
+| updateUserId | Long | @LastModifiedBy | @Column(name="update_user_id") | update_user_id | bigint | 最后修改人 ID |
+| createTime | Instant | @CreatedDate | @Column(name="create_time") | create_time | timestamptz | 创建时间 |
+| updateTime | Instant | @LastModifiedDate | @Column(name="update_time") | update_time | timestamptz | 最后修改时间 |
+| deleted | Boolean | — | @Column(name="is_deleted") | is_deleted | boolean default false | 软删除标记 |
+| version | Integer | @Version | @Column(name="version") | version | integer | 乐观锁 |
+
+`deleted`(映射 `is_deleted`)为软删除标记,业务显式控制,不走 Auditing。`version` 为乐观锁字段,使用 JPA ` @Version` 自动管理,不走 Auditing。
+
+审计字段 + deleted + version 提取到 `BaseAuditPO` 抽象基类(` @MappedSuperclass` + ` @EntityListeners(AuditingEntityListener.class)`),所有业务 PO 继承即可。
+
+### 6.3 关于 create_user / update_user(用户名)
+
+M2 基线仅存储 userId(create_user_id / update_user_id)。用户名通过关联查询获取,避免冗余不一致。如公司规范强制要求存储用户名,可在 BaseAuditPO 中扩展 createUser / updateUser 字段(varchar),通过 ` @PrePersist` / ` @PreUpdate` 从 TenantContext 获取。此为可选扩展,不影响基线。
+
+## 七、异常体系与错误映射
+
+### 7.1 ErrorCode 设计(冻结——唯一映射源)
+
+ErrorCode 枚举是 HTTP Status + 默认 message 的**唯一真相源**。GlobalExceptionHandler 只负责将 ErrorCode 翻译为 ApiResponse,不维护独立的映射表。新增 bounded context 时只需扩展 ErrorCode 枚举,Handler 无需改动。
+
+**ErrorCode 枚举代码结构冻结**:
+
+```
+public enum ErrorCode {
+
+ COMMON_INVALID_PARAM(400, "参数校验失败"),
+ ORDER_NOT_FOUND(404, "订单不存在"),
+ ...;
+
+ private final int httpStatus;
+ private final String defaultMessage;
+
+ // 对外序列化:errorCode 字段取值为 name()(即枚举常量名)
+ // 不额外定义 code 字符串字段,直接使用 Java Enum 自带的 name()
+}
+```
+
+### 7.2 统一错误响应
+
+```json
+{
+ "code": 404,
+ "errorCode": "ORDER_NOT_FOUND",
+ "message": "订单不存在",
+ "details": "Order with id=xxx not found(仅非 prod 展示)",
+ "traceId": "abc123",
+ "timestamp": "2026-02-12T10:00:00Z"
+}
+```
+
+`code` 固定为 HTTP 状态码整数(由 `ErrorCode.httpStatus` 决定),`errorCode` 为 `ErrorCode.name()`,前端按 `errorCode` 做分支判断。`details` 在 prod 环境下隐藏(返回 null)。`message` 取 DomainException.detail(若有),否则取 ErrorCode.defaultMessage。
+
+### 7.3 M2 基线 ErrorCode 清单
+
+| errorCode (name()) | httpStatus | defaultMessage |
+|---|---|---|
+| COMMON_INVALID_PARAM | 400 | 参数校验失败 |
+| COMMON_NOT_FOUND | 404 | 资源不存在 |
+| COMMON_INTERNAL_ERROR | 500 | 系统内部错误 |
+| COMMON_OPTIMISTIC_LOCK | 409 | 数据已被修改,请刷新重试 |
+| AUTH_MISSING_TENANT | 400 | 缺少租户标识 |
+| AUTH_MISSING_DEPT | 400 | 缺少部门标识 |
+| AUTH_INVALID_USER_ID | 400 | 用户标识格式不合法 |
+| AUTH_HEADER_CONFLICT | 400 | 请求头信息冲突 |
+| AUTH_LEGACY_HEADER_NOT_ALLOWED | 400 | 不再支持的旧版请求头 |
+| AUTH_ACCESS_DENIED | 403 | 无权限 |
+| AUTH_FORBIDDEN_SCOPE | 403 | 部门范围越权 |
+| ORDER_NOT_FOUND | 404 | 订单不存在(示例) |
+| ORDER_STATUS_INVALID | 422 | 订单状态不合法(示例) |
+| ORDER_AMOUNT_INVALID | 422 | 订单金额不合法(示例) |
+
+**注意**:`LEGACY_HEADER_REDUNDANT` 和 `LEGACY_HEADER_FALLBACK` **不进入 ErrorCode 枚举**。它们是日志/指标维度的标识,定义为独立的 `SecurityLogMarker` 常量类,Micrometer counter 使用相同标识名(如 `security.header.legacy_redundant`)。
+
+### 7.4 ErrorCode 新增规则(冻结)
+
+新增 ErrorCode 必须遵循前缀命名(`{BOUNDED_CONTEXT}_*`)。新增后必须在 errors.md 中同步更新。ErrorCode 只允许新增和标记 @Deprecated,不允许删除或修改已有枚举项的 name() / httpStatus。涉及前端/网关行为变更的 ErrorCode 变更必须通知相关方。
+
+## 八、请求生命周期(一次 POST /api/v1/orders 的完整链路)
+
+```
+客户端
+ │
+ ▼
+网关(鉴权、限流、路由)
+ │ 注入 Header: X-Tenant-Id / X-Dept-Id / X-User-Id / X-User-Roles
+ ▼
+┌─ tmpl-api ────────────────────────────────────────────────────────┐
+│ │
+│ TraceIdFilter [Ordered.HIGHEST_PRECEDENCE] │
+│ → 读取或生成 traceId → MDC.put("traceId") │
+│ │
+│ TenantContextFilter [Ordered.HIGHEST_PRECEDENCE + 1] │
+│ → 按优先级 1-8 解析 Header(见 5.3 失败策略表) │
+│ → X-User-Id 解析为 Long,失败 → 400 AUTH_INVALID_USER_ID │
+│ → 填充 TenantContext(ThreadLocal) │
+│ → MDC.put("tenantId", "deptId", "userId") │
+│ → finally { TenantContext.clear(); │
+│ MDC.remove("traceId"/"tenantId"/"deptId"/"userId"); }│
+│ │
+│ OrderController │
+│ → 接收 CreateOrderRequest(不含租户字段, @Valid 校验) │
+│ → TenantScope scope = TenantContext.currentScope() │
+│ → 构建 CreateOrderCommand │
+│ → 调用 createOrderUseCase.execute(command, scope) │
+│ → 包装为 ApiResponse.created(OrderResponse) │
+│ │
+└─────────────────────────────┬──────────────────────────────────────┘
+ │
+┌─ tmpl-application ──────────▼──────────────────────────────────────┐
+│ │
+│ CreateOrderUseCase │
+│ @Transactional │
+│ → 校验 Command │
+│ → Order.create(OrderId.generate(), scope.tenantId(), ...) │
+│ → orderRepository.save(order, scope.tenantId()) │
+│ → eventPublisher.publish(order.domainEvents()) │
+│ → 返回 OrderDetail │
+│ │
+└─────────────────────────────┬──────────────────────────────────────┘
+ │
+┌─ tmpl-domain ───────────────▼──────────────────────────────────────┐
+│ │
+│ Order.create(id, tenantId, items, totalAmount) │
+│ → totalAmount == null → 抛 ORDER_AMOUNT_INVALID │
+│ → totalAmount < 0 → 抛 ORDER_AMOUNT_INVALID │
+│ → totalAmount == 0 → 允许 │
+│ → 设置状态 CREATED │
+│ → 注册 OrderCreatedEvent │
+│ → 返回 Order 聚合 │
+│ │
+│ Order 聚合仅含业务字段,不含审计列(审计由 PO 层承担) │
+│ │
+└─────────────────────────────┬──────────────────────────────────────┘
+ │
+┌─ tmpl-infrastructure ───────▼──────────────────────────────────────┐
+│ │
+│ OrderPersistenceAdapter (adapter 包) │
+│ → 注入 OrderDao(internal 包,public 类) │
+│ → 编译期无法访问 OrderJpaRepository(internal 包,package-private)│
+│ → OrderPersistenceMapper: Domain Order → OrderPO │
+│ → OrderPO 继承 BaseAuditPO(审计字段自动填充) │
+│ → dept_id 写入真实部门 ID,不写入 "0" │
+│ → 不声明 @Transactional(事务由 UseCase 控制) │
+│ │
+│ LoggingOrderEventPublisher implements DomainEventPublisher │
+│ → 结构化 JSON 日志输出事件(M3 可替换为 MQ) │
+│ │
+└─────────────────────────────┬──────────────────────────────────────┘
+ │
+ ▼
+ PostgreSQL
+ (Flyway 管理 schema)
+```
+
+## 九、CI 流水线(Gitea Actions)
+
+### 9.1 Stage 结构(冻结——单 Job 多 Step + 独立 Job)
+
+Stage 1-3 合并为同一个 Job(quality-gate)的顺序 Step,共享 workspace。Stage 4-5 为独立 Job(build-and-deploy),仅在 main/release 分支触发,通过 `needs` 依赖 quality-gate Job。
+
+```
+ push / PR (所有分支)
+ │
+ ▼
+ ┌─ Job: quality-gate (所有分支) ──────────────────────────┐
+ │ │
+ │ Step 1: Compile + Unit Test + ArchUnit │
+ │ mvn -T1C clean test │
+ │ JaCoCo agent 挂载,生成覆盖率 exec 文件 │
+ │ │
+ │ Step 2: Format Check │
+ │ mvn spotless:check │
+ │ 不触发编译,纯文本扫描,零容忍 │
+ │ │
+ │ Step 3: Quality Gate │
+ │ mvn verify -DskipTests │
+ │ 复用 Step 1 编译产出(同一 workspace,无需 artifact) │
+ │ SpotBugs(基于 class) │
+ │ OWASP Dependency Check(基于依赖树) │
+ │ JaCoCo 覆盖率门槛校验 ≥ 70%(基于 Step 1 的 exec) │
+ │ │
+ └──────────────────────┬───────────────────────────────────┘
+ │ pass
+ ┌──────────────────────▼───────────────────────────────────┐
+ │ Job: build-and-deploy (仅 main / release 分支) │
+ │ needs: [quality-gate] │
+ │ │
+ │ Step 4: Build Image │
+ │ docker build → 多阶段构建 │
+ │ 内部 mvn -DskipTests package(CI 已保证测试通过) │
+ │ docker tag ${GIT_SHA_SHORT} │
+ │ docker push harbor.ljwx.com/library/${service}:${sha} │
+ │ │
+ │ Step 5: Update Deploy Repo │
+ │ 幂等脚本: │
+ │ clone ${service}-deploy │
+ │ yq 精确替换 overlays/${env}/kustomization.yaml │
+ │ 仅修改当前 service 的标签,不影响其他 service │
+ │ git commit + push → 触发 ArgoCD 同步 │
+ │ 分支 → 环境映射:main → dev,release/* → staging/prod │
+ │ │
+ └───────────────────────────────────────────────────────────┘
+```
+
+### 9.2 设计要点
+
+quality-gate Job 对所有分支和 PR 生效。build-and-deploy Job 仅在 main/release 分支合并后触发。Step 1-3 共享同一 workspace,JaCoCo exec、编译后 class、surefire 报告均可直接被后续 Step 读取。build-and-deploy Job 通过 `needs: [quality-gate]` 声明依赖,quality-gate 失败则不执行。
+
+## 十、部署架构(ArgoCD + Kustomize)
+
+### 10.1 部署仓库结构
+
+```
+${service-name}-deploy/
+│
+├── base/
+│ ├── deployment.yaml ← 通用 Deployment 模板
+│ ├── service.yaml ← ClusterIP Service
+│ ├── configmap.yaml ← 通用配置(TZ=UTC 等)
+│ ├── hpa.yaml ← HPA 模板(min/max 由 overlay 覆盖)
+│ └── kustomization.yaml
+│
+└── overlays/
+ ├── dev/
+ │ ├── kustomization.yaml ← replicas:1, 镜像标签, SPRING_PROFILES_ACTIVE=dev
+ │ ├── configmap-patch.yaml
+ │ └── resource-patch.yaml
+ ├── staging/
+ │ ├── kustomization.yaml ← replicas:2, staging profile
+ │ └── ...
+ └── prod/
+ ├── kustomization.yaml ← replicas 按需, prod profile, 资源上限
+ └── ...
+```
+
+### 10.2 ArgoCD Application 命名
+
+每个环境一个 Application,命名为 `${service-name}-${env}`(如 `order-service-dev`)。监听对应 overlays 目录。
+
+### 10.3 Deployment 默认配置
+
+优雅停机:preStop hook(sleep 5)+ terminationGracePeriodSeconds: 30 + `spring.lifecycle.timeout-per-shutdown-phase=25s`。健康探针:livenessProbe → `/actuator/health/liveness`,readinessProbe → `/actuator/health/readiness`,启动延迟 initialDelaySeconds: 30。环境变量注入:SPRING_PROFILES_ACTIVE、LOG_FORMAT=json(prod)、TZ=UTC、JAVA_OPTS(JVM 参数)。资源 requests/limits 由 overlay 覆盖,base 提供合理默认值。
+
+## 十一、Docker 镜像策略
+
+采用多阶段构建。第一阶段使用 Maven + JDK 21 编译打包(`mvn -DskipTests package`)。第二阶段使用 Eclipse Temurin 21 JRE 作为运行时镜像。最终镜像不包含源码、构建工具和完整 JDK,体积控制在 200MB 以内。
+
+**skipTests 安全性保证**:Docker build 仅在 CI 的 build-and-deploy Job 中触发,该 Job 通过 `needs: [quality-gate]` 依赖 quality-gate Job 全部通过。
+
+镜像标签固定为 git short SHA(7 位),不使用 latest,不使用分支名。通过 `spring-boot-maven-plugin` 的 `build-info` goal 和 `git-commit-id-maven-plugin` 生成构建信息,`/actuator/info` 端点返回 commit SHA、构建时间、Spring Boot 版本。
+
+## 十二、日志策略
+
+### 12.1 依赖与格式
+
+依赖 `net.logstash.logback:logstash-logback-encoder`(版本在父 POM 锁定)。dev profile 使用 ConsoleAppender + PatternLayout(人类可读),非 dev profile 使用 ConsoleAppender + LogstashEncoder(JSON 输出到 stdout,由 Loki/Promtail 采集)。
+
+### 12.2 固定 MDC 字段
+
+| MDC Key | 注入位置 | 清理方式 | 说明 |
+|---|---|---|---|
+| traceId | TraceIdFilter | MDC.remove("traceId") | 请求链路 ID |
+| tenantId | TenantContextFilter | MDC.remove("tenantId") | 租户 |
+| deptId | TenantContextFilter | MDC.remove("deptId") | 部门 |
+| userId | TenantContextFilter | MDC.remove("userId") | 操作人(Long → String) |
+
+### 12.3 日志/指标标识常量(非 ErrorCode)
+
+以下标识不进入 ErrorCode 枚举,定义在独立的 `SecurityLogMarker` 常量类中,用于 WARN 日志和 Micrometer counter:
+
+| 常量名 | 用途 | Micrometer counter 名 |
+|---|---|---|
+| LEGACY_HEADER_FALLBACK | 兼容模式回退旧 Header | security.header.legacy_fallback |
+| LEGACY_HEADER_REDUNDANT | 新旧 Header 同时存在且一致 | security.header.legacy_redundant |
+| DEPT_SCOPE_ALL_DEV_ONLY | dev 环境 deptId=0 放行 | security.scope.dept_all_dev |
+
+### 12.4 可选业务字段
+
+在代码中按需 `MDC.put`:costMs、sqlId、rows、method、requestUri、status、spanId(为 OTel 预留)。
+
+### 12.5 慢查询
+
+阈值可配置(`app.observability.slow-query-threshold-ms`),默认 200ms。超阈值输出 WARN 日志,包含 sqlId、costMs、rows。
+
+## 十三、应用仓库完整目录结构
+
+```
+java-service-template/
+│
+├── pom.xml ← 父 POM:版本锁定、插件统一配置
+│ groupId: com.ljwx.tmpl
+│
+├── tmpl-domain/
+│ ├── pom.xml ← 零框架依赖(仅 jakarta.validation-api)
+│ └── src/main/java/com/ljwx/tmpl/domain/
+│ ├── exception/
+│ │ ├── ErrorCode.java ← 枚举:httpStatus + defaultMessage
+│ │ └── DomainException.java ← errorCode + detail
+│ ├── model/
+│ │ ├── Order.java ← 聚合根(示例),纯业务字段
+│ │ ├── OrderId.java ← 值对象
+│ │ ├── Money.java ← 值对象
+│ │ └── OrderStatus.java ← 枚举
+│ ├── event/
+│ │ ├── DomainEvent.java ← 标记接口
+│ │ └── OrderCreatedEvent.java ← 示例事件
+│ └── repository/
+│ └── OrderRepository.java ← 接口,方法显式带 tenantId
+│
+├── tmpl-application/
+│ ├── pom.xml ← 依赖 tmpl-domain + spring-context + spring-tx
+│ └── src/main/java/com/ljwx/tmpl/application/
+│ ├── port/
+│ │ ├── in/
+│ │ │ ├── CreateOrderUseCase.java
+│ │ │ └── GetOrderUseCase.java
+│ │ └── out/
+│ │ └── DomainEventPublisher.java ← 接口
+│ ├── command/
+│ │ └── CreateOrderCommand.java
+│ ├── query/
+│ │ └── OrderDetailQuery.java
+│ ├── dto/
+│ │ ├── OrderDetail.java
+│ │ └── PageResult.java
+│ ├── context/
+│ │ ├── TenantContext.java ← ThreadLocal,userId 为 Long
+│ │ └── TenantScope.java ← record(tenantId, deptId, userId, roles)
+│ └── service/
+│ ├── CreateOrderService.java ← implements CreateOrderUseCase
+│ └── GetOrderService.java ← implements GetOrderUseCase
+│
+├── tmpl-infrastructure/
+│ ├── pom.xml ← 依赖 tmpl-application + JPA + Redis 等
+│ └── src/main/java/com/ljwx/tmpl/infrastructure/
+│ ├── persistence/
+│ │ ├── entity/
+│ │ │ ├── BaseAuditPO.java ← @MappedSuperclass + JPA Auditing
+│ │ │ │ Long createUserId ( @CreatedBy, @Column("create_user_id"))
+│ │ │ │ Long updateUserId ( @LastModifiedBy, @Column("update_user_id"))
+│ │ │ │ Instant createTime ( @CreatedDate, @Column("create_time"))
+│ │ │ │ Instant updateTime ( @LastModifiedDate, @Column("update_time"))
+│ │ │ │ Boolean deleted ( @Column("is_deleted"))
+│ │ │ │ Integer version ( @Version)
+│ │ │ └── OrderPO.java ← 继承 BaseAuditPO
+│ │ ├── converter/
+│ │ │ └── OrderStatusConverter.java
+│ │ ├── order/
+│ │ │ ├── internal/ ← JpaRepository + Dao 同包
+│ │ │ │ ├── OrderJpaRepository.java ← package-private
+│ │ │ │ └── OrderDao.java ← public,仅暴露带 tenant 方法
+│ │ │ └── adapter/
+│ │ │ └── OrderPersistenceAdapter.java ← 注入 OrderDao
+│ │ └── mapper/
+│ │ └── OrderPersistenceMapper.java ← MapStruct: Domain ↔ PO
+│ ├── audit/
+│ │ └── AuditorAwareImpl.java ← AuditorAware,无上下文→0L
+│ └── event/
+│ └── LoggingOrderEventPublisher.java
+│
+├── tmpl-api/
+│ ├── pom.xml ← 依赖全部模块 + Boot Starter + Flyway + Testcontainers
+│ └── src/
+│ ├── main/java/com/ljwx/tmpl/api/
+│ │ ├── TmplApplication.java ← @SpringBootApplication
+│ │ ├── controller/
+│ │ │ └── OrderController.java
+│ │ ├── dto/
+│ │ │ ├── CreateOrderRequest.java ← 不含 tenantId/deptId/userId
+│ │ │ ├── OrderResponse.java
+│ │ │ ├── ApiResponse.java ← 统一响应包装
+│ │ │ └── PageResponse.java ← 分页响应
+│ │ ├── mapper/
+│ │ │ └── OrderDtoMapper.java ← MapStruct
+│ │ ├── filter/
+│ │ │ ├── TraceIdFilter.java ← HIGHEST_PRECEDENCE
+│ │ │ └── TenantContextFilter.java ← HIGHEST_PRECEDENCE + 1
+│ │ │ finally { remove specific MDC keys }
+│ │ ├── exception/
+│ │ │ └── GlobalExceptionHandler.java ← 从 ErrorCode 取 httpStatus/message
+│ │ ├── marker/
+│ │ │ └── SecurityLogMarker.java ← 日志/指标标识常量(非 ErrorCode)
+│ │ └── config/
+│ │ ├── SecurityConfig.java
+│ │ ├── TenantHeaderProperties.java ← @ConfigurationProperties,默认 compat=false
+│ │ ├── TenantHeaderStartupValidator.java ← prod + compat=true → fail fast
+│ │ ├── OpenApiConfig.java
+│ │ ├── WebConfig.java
+│ │ └── JpaAuditingConfig.java ← @EnableJpaAuditing
+│ ├── main/resources/
+│ │ ├── application.yml ← 公共配置 + virtual threads
+│ │ ├── application-dev.yml ← header-compat: true
+│ │ ├── application-staging.yml ← header-compat 不出现
+│ │ ├── application-prod.yml ← header-compat 不出现
+│ │ ├── logback-spring.xml ← dev=pattern, 非dev=LogstashEncoder
+│ │ └── db/migration/
+│ │ ├── V1__init.sql ← 基础表(sample=none 时保留)
+│ │ └── V2__create_order_table.sql ← 示例表(sample=none 时删除)
+│ └── test/java/com/ljwx/tmpl/
+│ ├── architecture/
+│ │ └── ArchitectureTest.java ← A/B/C 类规则
+│ └── integration/
+│ ├── OrderApiIntegrationTest.java
+│ └── TenantFilterIntegrationTest.java
+│
+├── docs/
+│ ├── 00-index.md ← 文档入口索引
+│ ├── architecture-spec.md
+│ ├── repo-layout.md
+│ ├── contracts/
+│ │ ├── security-context.md
+│ │ ├── transactions.md
+│ │ ├── errors.md
+│ │ └── observability.md
+│ ├── adrs/
+│ │ └── ADR-0001-tenant-field-rename.md
+│ └── prompts/
+│ ├── P0_system_rules.md
+│ ├── P1_task_templates/
+│ │ ├── route_a_api_first.md
+│ │ └── route_b_domain_first.md
+│ └── P2_fixloop.md
+│
+├── .gitea/workflows/
+│ └── ci.yaml ← 2 Job: quality-gate + build-and-deploy
+├── Dockerfile ← 多阶段构建
+├── .editorconfig
+├── .gitignore
+├── init.sh
+└── README.md
+```
+
+## 十四、ArchUnit 规则分级
+
+### A 类(必须通过,CI 阻断)
+
+| 规则 | 说明 |
+|---|---|
+| domain 禁止 import Spring/JPA/Hibernate | `..domain..` 不得依赖 `org.springframework..`、`jakarta.persistence..`、`org.hibernate..` |
+| domain 禁止注入 Validator | `..domain..` 不得 import `jakarta.validation.Validator` 及其实现 |
+| application 禁止 import Web/Persistence/Security/Client | 白名单:Spring Context + TX |
+| @Transactional 仅限 application 层 | 扫描所有 ` @Transactional`,必须在 `..application..` 包内 |
+| 禁止字段注入 | 不允许 ` @Autowired` 用于字段 |
+| Controller 禁止依赖 infrastructure | `..api.controller..` 不得 import `..infrastructure..` |
+| Adapter 禁止 import JpaRepository | `..adapter..` 不得 import `..internal..*JpaRepository` |
+| Adapter 不得位于 internal 包 | `..adapter..` 包路径不得包含 `..internal..` |
+| @Query(nativeQuery=true) 必须含 tenant_id | 原生 SQL 必须包含物理列 `tenant_id` |
+| @Query(JPQL) 必须含 tenantId | JPQL 必须包含 `:tenantId` 或实体字段 `tenantId` |
+
+### B 类(强烈推荐,覆盖 ≥ 80%)
+
+| 规则 | 说明 |
+|---|---|
+| UseCase 入口方法命名 | public 方法必须名为 `execute` |
+| 测试类命名 | *Test / *IT 结尾 | ArchUnit `TestNamingConventionTest`(扫描 test classpath)或 CI 门禁正则 |
+
+### C 类(可选,逐步启用)
+
+| 规则 | 说明 |
+|---|---|
+| 异步代码禁止 TenantContext.current() | 标注 @Async 的方法内禁止调用 |
+| Controller 参数必须 @Valid | 所有 @RequestBody 参数 |
+
+## 十五、init.sh 行为规范
+
+### 15.1 参数
+
+| 参数 | 必填 | 默认值 | 说明 |
+|---|---|---|---|
+| --service | 是 | — | 服务名(如 order-service) |
+| --package | 否 | com.ljwx | 包根 |
+| --sample | 否 | order | order 或 none |
+| --no-table-prefix | 否 | 不开启 | 去掉 t_ 前缀 |
+
+### 15.2 替换范围
+
+目录名:`tmpl-*` → `${service}-*`。pom.xml:artifactId、groupId、module name。Java 包名:`com.ljwx.tmpl` → `${package}.${service-dotted}`。Dockerfile:镜像路径。Kustomize 引用:镜像名。README:项目名。.gitea/workflows/ci.yaml:service 名变量。
+
+### 15.3 --sample=none 行为
+
+删除 Order 相关所有 Java 文件。删除 `V2__create_order_table.sql`。保留 `V1__init.sql`。保留 ArchUnit 基础规则。保留 Filter、ExceptionHandler、Config、ApiResponse、BaseAuditPO、AuditorAwareImpl、TenantContext 等基础设施。Actuator /actuator/health 作为启动后可验证端点。`mvn clean test` 必须全绿。
+
+### 15.4 输出验收清单
+
+init.sh 执行完毕后打印:
+
+```
+✅ 初始化完成:${service-name}
+ groupId: ${groupId}
+ package: ${package}.${service-dotted}
+ 镜像仓库: harbor.ljwx.com/library/${service-name}
+ 镜像标签: 由 CI 生成(格式: ${GIT_SHA_SHORT},不使用 latest)
+
+验证步骤:
+ 1. mvn clean test ← 全部绿灯
+ 2. docker build -t ${service}:local . ← 本地构建成功
+ 3. docker run ... → curl http://localhost:8080/actuator/health ← {"status":"UP"}
+
+提示:镜像推送由 CI build-and-deploy Job 自动完成,标签为 git commit short SHA。
+```
+
+## 十六、M2 Frozen Contract(不可变契约)
+
+### 16.1 变更冻结项的强制伴随变更
+
+任何 PR 修改以下冻结项时,必须同时更新对应位置:
+
+| 变更范围 | 必须同步更新 |
+|---|---|
+| Header 规则 / 租户字段名 | docs/contracts/security-context.md + TenantFilterIntegrationTest + README |
+| 层级依赖规则 | docs/architecture-spec.md + ArchitectureTest + README |
+| 审计字段 | Flyway migration + BaseAuditPO + AuditorAwareImpl + 保存实体的集成测试断言 |
+| ErrorCode 变更 | ErrorCode 枚举 + docs/contracts/errors.md |
+| CI Stage / 门禁阈值 | ci.yaml + docs/architecture-spec.md + README |
+| 镜像标签规则 | Dockerfile + ci.yaml + deploy repo kustomization + docs/contracts/observability.md |
+
+### 16.2 ADR 要求
+
+凡是改动租户模型、事务边界、错误码体系、CI 门禁、镜像策略的 PR,必须在 `docs/adrs/` 中补充 ADR 记录,格式为 `ADR-{序号}-{简述}.md`,包含:背景、决策、后果、替代方案。
+
+### 16.3 ErrorCode 不可变性
+
+ErrorCode 只允许新增和标记 @Deprecated,不允许删除或修改已有枚举项的 name() / httpStatus。
+
+## 十七、关键设计决策索引
+
+### 核心冻结项(31 项)
+
+| # | 决策项 | 结论 | 契约来源 |
+|---|---|---|---|
+| 1 | 默认包根 | com.ljwx | architecture-spec.md |
+| 2 | 默认 groupId | com.ljwx.${service} | architecture-spec.md |
+| 3 | Spring Boot 版本 | 父 POM 锁定 GA 版本(以 POM 为准) | architecture-spec.md |
+| 4 | 租户字段命名 | tenantId / deptId(弃用 customerId / orgId) | security-context.md |
+| 5 | 租户物理列 | tenant_id varchar(64) / dept_id varchar(64) | security-context.md |
+| 6 | userId 类型 | Long(与审计列 bigint 对齐) | security-context.md |
+| 7 | Header 标准 | X-Tenant-Id / X-Dept-Id / X-User-Id / X-User-Roles | security-context.md |
+| 8 | Header 兼容窗口 | dev 回退(WARN),prod 配置项不出现 + fail fast 校验 | security-context.md |
+| 9 | 缺 Header 失败策略 | 非兼容模式直接 400 | security-context.md |
+| 10 | prod 仅有旧 Header | 400 AUTH_LEGACY_HEADER_NOT_ALLOWED | security-context.md |
+| 11 | 新旧 Header 同时存在且一致 | 允许通过,WARN + counter(迁移观测) | security-context.md |
+| 12 | Header 冲突 | 新旧不一致 → 400 AUTH_HEADER_CONFLICT | security-context.md |
+| 13 | deptId=0 语义 | 跨部门,需 SCOPE_ALL_DEPT 权限 | security-context.md |
+| 14 | deptId 存储规则 | dept_id 列永远存真实部门 ID,不写入 "0" | security-context.md |
+| 15 | 服务内权限边界 | 只做 scope validation,不做 authorization decision | security-context.md |
+| 16 | 租户字段来源 | 仅从网关 Header 解析,DTO 禁止携带 | security-context.md |
+| 17 | TenantContext 清理 | finally 块 remove 具体 MDC key,不用 MDC.clear() | security-context.md |
+| 18 | 事务声明位置 | 仅 application 层 UseCase | transactions.md |
+| 19 | 审计实现 | Spring Data JPA Auditing,AuditorAware\ | architecture-spec.md |
+| 20 | 审计字段 | create_user_id(bigint) / update_user_id(bigint) / create_time(timestamptz) / update_time(timestamptz) | architecture-spec.md |
+| 21 | 系统审计 ID | 无上下文时 userId = 0L | architecture-spec.md |
+| 22 | 软删除 | Java 字段 deleted, @Column(name="is_deleted"),业务显式控制 | architecture-spec.md |
+| 23 | 乐观锁 | @Version,JPA 自动管理 | transactions.md |
+| 24 | 主键类型 | 领域 String(VO),PO VARCHAR(64) | architecture-spec.md |
+| 25 | Repository 安全 | Dao 封装 + internal 包 package-private JpaRepository | security-context.md |
+| 26 | @Query 租户检查 | JPQL 含 tenantId/:tenantId,native SQL 含 tenant_id | security-context.md |
+| 27 | ErrorCode 设计 | 枚举自带 httpStatus + defaultMessage,name() 即 errorCode | errors.md |
+| 28 | ErrorCode 不可变性 | 只新增/Deprecated,不删除不改 name/httpStatus | errors.md |
+| 29 | 日志格式 | logstash-logback-encoder JSON;dev 用 pattern | observability.md |
+| 30 | MDC 字段 | traceId / tenantId / deptId / userId | observability.md |
+| 31 | 镜像标签 | git short SHA(7位),写入 actuator/info,不使用 latest | observability.md |
+
+### 扩展冻结项(7 项)
+
+| # | 决策项 | 结论 | 契约来源 |
+|---|---|---|---|
+| 32 | CI 结构 | 单 Job 多 Step(quality-gate)+ 独立 Job(build-and-deploy) | architecture-spec.md |
+| 33 | Docker build 安全性 | skipTests,仅在 quality-gate 通过后触发 | architecture-spec.md |
+| 34 | domain Jakarta Validation | 仅允许注解,禁止注入 Validator | architecture-spec.md |
+| 35 | application 层依赖口径 | domain + Spring Context/TX,禁止 Web/Persistence/Security/Client | architecture-spec.md |
+| 36 | Virtual Threads | 默认开启 spring.threads.virtual.enabled=true | architecture-spec.md |
+| 37 | 表前缀 | t_(可通过 --no-table-prefix 关闭) | architecture-spec.md |
+| 38 | Sample 模块 | --sample=order 默认,none 保留 V1__init.sql + 基础设施 | repo-layout.md |
+
+## 十八、实施顺序与验收点
+
+| 批次 | 交付内容 | 验收点 |
+|---|---|---|
+| 1 | 父 POM + 四模块骨架 + TmplApplication + Actuator | `mvn clean test` 通过;启动后 `/actuator/health` 返回 UP |
+| 2 | ErrorCode + DomainException + TenantContext + TenantScope + SecurityLogMarker | 编译通过;domain/application 单测跑通 |
+| 3 | TraceIdFilter + TenantContextFilter + TenantHeaderProperties + StartupValidator + TenantFilterIntegrationTest | 集成测试覆盖:缺失/冲突/冗余旧 Header/异常清理/prod fail-fast |
+| 4 | ArchitectureTest(A 类) + Gitea CI quality-gate Job | 本地 `mvn test` 触发 ArchUnit;Gitea quality-gate 全绿 |
+| 5 | Flyway V1/V2 + BaseAuditPO + AuditorAware + Order 示例 CRUD + 集成测试 | 保存 Order 断言 create_user_id / tenant_id 写入正确 |
+| 6 | Dockerfile + build-and-deploy Job + deploy repo Kustomize + init.sh | main 分支推送后镜像推送成功;deploy repo 标签更新幂等;init.sh --sample=none 后 CI 全绿 |
+
+---
+
+## 租户 Header 信任边界
+
+`X-Tenant-Id` Header 在 tmpl-api 内部被视为已验证的值。**但服务本身不负责校验
+Header 的真实性**。在生产部署中,必须由 API Gateway 或认证层从已验证的
+JWT Token 中提取并注入,不得信任客户端直接传入的值。tmpl-api 假设上游已完成校验。
+
+信任链路:Client → API Gateway(校验 JWT → 提取 tenantId → 注入/覆盖 Header)→
+tmpl-api(读取 Header → 存入 TenantContext → 附加到所有查询条件)。
+
+详细安全设计参见 [security.md](security.md) 第 2 节。
\ No newline at end of file
diff --git a/docs/database.md b/docs/database.md
new file mode 100644
index 0000000..5edbd5d
--- /dev/null
+++ b/docs/database.md
@@ -0,0 +1,131 @@
+# 数据库设计与迁移 (Database)
+
+---
+
+## 1. 技术选型
+
+| 项 | 选择 | 说明 |
+|----|------|------|
+| 数据库 | PostgreSQL 15+ | JSONB 支持、RLS、分区表 |
+| 连接池 | HikariCP(Spring Boot 默认) | 高性能、低开销 |
+| 迁移工具 | Flyway | Forward-only、版本化迁移 |
+| ORM | Spring Data JPA + Hibernate | 通过 MapStruct 转换 PO ↔ Domain |
+
+---
+
+## 2. Schema 设计原则
+
+### 2.1 多租户隔离
+
+所有业务表必须包含 `tenant_id BIGINT NOT NULL` 列。所有查询条件必须包含 `tenant_id`,通过 Repository 方法签名强制(如 `findByTenantIdAndId`)。
+
+建议在生产环境启用 PostgreSQL Row-Level Security (RLS) 作为最后防线:
+
+```sql
+ALTER TABLE t_order ENABLE ROW LEVEL SECURITY;
+CREATE POLICY tenant_isolation ON t_order
+ USING (tenant_id = current_setting('app.current_tenant')::BIGINT);
+```
+
+### 2.2 审计字段
+
+所有表通过 JPA `@MappedSuperclass`(`BaseAuditPO`)自动维护:
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `create_time` | `TIMESTAMP NOT NULL DEFAULT now()` | 创建时间 |
+| `update_time` | `TIMESTAMP NOT NULL DEFAULT now()` | 最后更新时间 |
+| `deleted` | `BOOLEAN NOT NULL DEFAULT false` | 软删除标记 |
+
+### 2.3 软删除
+
+业务表使用 `deleted` 字段实现软删除。所有查询默认排除已删除记录(通过 Hibernate `@Where` 或手动条件)。物理删除仅在数据归档场景下使用。
+
+---
+
+## 3. 索引策略
+
+### 3.1 通用规则
+
+每张表的主键使用自增 `BIGSERIAL` 或 `UUID`(根据业务选择)。`tenant_id` 必须出现在复合索引的最左列。常用查询条件组合创建复合索引,遵循最左前缀原则。
+
+### 3.2 t_order 表索引示例
+
+```sql
+-- 主键索引(自动)
+-- 租户 + 订单号唯一索引
+CREATE UNIQUE INDEX uk_order_tenant_no ON t_order(tenant_id, order_no);
+
+-- 租户 + 状态(列表查询)
+CREATE INDEX idx_order_tenant_status ON t_order(tenant_id, status);
+
+-- 租户 + 创建时间(分页排序)
+CREATE INDEX idx_order_tenant_create_time ON t_order(tenant_id, create_time DESC);
+```
+
+### 3.3 索引维护
+
+定期检查未使用的索引:
+
+```sql
+SELECT schemaname, relname, indexrelname, idx_scan
+FROM pg_stat_user_indexes
+WHERE idx_scan = 0
+ORDER BY relname;
+```
+
+---
+
+## 4. Flyway 迁移规范
+
+### 4.1 文件命名
+
+```
+V{版本号}__{描述}.sql -- 版本化迁移(不可修改)
+R__{描述}.sql -- 可重复迁移(如视图、存储过程)
+```
+
+版本号格式:`V1__create_order_table.sql`、`V2__add_order_status_index.sql`。
+
+描述使用下划线分隔的蛇形命名(snake_case)。
+
+### 4.2 编写规则
+
+每个迁移文件只做一件事(一张表、一个索引、一次 ALTER)。所有 DDL 必须是幂等的(使用 `IF NOT EXISTS`、`IF EXISTS`)。禁止在迁移中使用 `DROP TABLE`(除非有审批的变更单号且附带数据备份计划)。DML 迁移(数据修复)必须包含 `WHERE` 条件,禁止无条件 `UPDATE` / `DELETE`。
+
+### 4.3 不可逆性说明
+
+Flyway 迁移是 forward-only 的。一旦迁移文件被合并到 `main` 分支并在任何环境执行过,就不得修改其内容(会导致 checksum 不匹配)。
+
+若需要回退 schema 变更,必须创建新的迁移文件(新版本号)执行反向 DDL。建议采用 expand-then-contract 模式:先在新迁移中添加新列 → 部署新代码 → 确认稳定后在后续迁移中移除旧列。
+
+### 4.4 迁移测试
+
+所有迁移脚本在 CI 中通过 Testcontainers 验证:自动启动 PostgreSQL 15 容器 → 应用全部迁移 → 运行集成测试。参见 [testing.md](testing.md)。
+
+---
+
+## 5. 连接池配置
+
+| 参数 | dev | staging | prod | 说明 |
+|------|-----|---------|------|------|
+| `maximumPoolSize` | 5 | 10 | 20 | 最大连接数 |
+| `minimumIdle` | 2 | 5 | 10 | 最小空闲连接 |
+| `connectionTimeout` | 30000ms | 20000ms | 10000ms | 获取连接超时 |
+| `idleTimeout` | 600000ms | 300000ms | 300000ms | 空闲连接存活时间 |
+| `maxLifetime` | 1800000ms | 1800000ms | 1800000ms | 连接最大生命周期 |
+
+> **注意**:`maximumPoolSize` × Pod 副本数不得超过数据库 `max_connections`。
+> 建议预留 20% 给运维工具和 Flyway 迁移。
+
+---
+
+## 6. 数据备份策略(建议)
+
+| 类型 | 频率 | 保留期 |
+|------|------|--------|
+| 全量备份 (pg_dump) | 每日 | 30 天 |
+| WAL 归档 | 持续 | 7 天 |
+| 逻辑备份 (关键表) | 每周 | 90 天 |
+
+建议使用 PgBackRest 或云厂商托管备份服务。恢复演练至少每季度执行一次。
\ No newline at end of file
diff --git a/docs/observability.md b/docs/observability.md
new file mode 100644
index 0000000..e2d395f
--- /dev/null
+++ b/docs/observability.md
@@ -0,0 +1,283 @@
+# 可观测性 (Observability)
+
+本文档定义 tmpl-api 服务的指标 (Metrics)、日志 (Logs)、追踪 (Traces) 规范以及
+告警策略和 SLO/SLA 目标。
+
+---
+
+## 1. 三大支柱概览
+
+| 支柱 | 工具 | 传输方式 |
+|------|------|----------|
+| Metrics | Micrometer → Prometheus | Pull(`/actuator/prometheus`,管理端口 9090) |
+| Logs | SLF4J + Logback → JSON stdout | 由 Kubernetes 日志驱动收集(Fluentd/Promtail → Loki/ES) |
+| Traces | Micrometer Tracing + OTLP(规划中) | Push to Tempo/Jaeger via OTLP gRPC |
+
+---
+
+## 2. Prometheus 指标清单
+
+### 2.1 JVM & 系统指标(Spring Boot Actuator 自动注册)
+
+| 指标名 | 类型 | 说明 |
+|--------|------|------|
+| `jvm_memory_used_bytes` | Gauge | JVM 各区域已用内存 |
+| `jvm_memory_max_bytes` | Gauge | JVM 各区域最大内存 |
+| `jvm_gc_pause_seconds` | Summary | GC 暂停时间 |
+| `jvm_threads_live_threads` | Gauge | 活跃线程数 |
+| `process_cpu_usage` | Gauge | 进程 CPU 使用率 |
+| `system_cpu_usage` | Gauge | 系统 CPU 使用率 |
+
+### 2.2 HTTP 请求指标
+
+| 指标名 | 类型 | 标签 | 说明 |
+|--------|------|------|------|
+| `http_server_requests_seconds` | Timer | `method`, `uri`, `status`, `outcome` | 请求延迟与计数 |
+
+### 2.3 数据源指标
+
+| 指标名 | 类型 | 说明 |
+|--------|------|------|
+| `hikaricp_connections_active` | Gauge | 活跃连接数 |
+| `hikaricp_connections_idle` | Gauge | 空闲连接数 |
+| `hikaricp_connections_pending` | Gauge | 等待连接数 |
+| `hikaricp_connections_timeout_total` | Counter | 获取连接超时次数 |
+| `hikaricp_connections_max` | Gauge | 最大连接池大小 |
+
+### 2.4 缓存指标(启用缓存时)
+
+| 指标名 | 类型 | 标签 | 说明 |
+|--------|------|------|------|
+| `cache_gets_total` | Counter | `cache`, `result` (hit/miss) | 缓存命中/未命中计数 |
+| `cache_puts_total` | Counter | `cache` | 缓存写入计数 |
+| `cache_evictions_total` | Counter | `cache` | 缓存驱逐计数 |
+
+### 2.5 自定义业务指标
+
+| 指标名 | 类型 | 标签 | 说明 |
+|--------|------|------|------|
+| `order_created_total` | Counter | `tenantId` | 各租户创建订单计数 |
+
+> **Cardinality 警告**:`tenantId` 标签的基数不应超过 200。若租户规模超过此上限,
+> 需改用直方图聚合或移除标签并通过日志分析实现。
+
+---
+
+## 3. 日志规范
+
+### 3.1 格式
+
+生产环境使用 JSON 格式输出至 stdout,由容器运行时收集。
+
+```
+{
+ "timestamp": "2026-02-12T10:30:00.123Z",
+ "level": "INFO",
+ "logger": "c.l.t.application.order.OrderAppService",
+ "message": "Order created",
+ "traceId": "abc123...",
+ "spanId": "def456...",
+ "tenantId": 5,
+ "orderId": 42
+}
+```
+
+### 3.2 级别策略
+
+| 级别 | 用途 | 示例 |
+|------|------|------|
+| ERROR | 不可恢复的异常,需要人工介入 | 数据库连接池耗尽 |
+| WARN | 可恢复的异常或降级行为 | Redis 连接失败、缓存降级 |
+| INFO | 关键业务事件 | 订单创建、状态变更 |
+| DEBUG | 开发调试信息 | SQL 参数、缓存 key |
+
+### 3.3 MDC 上下文
+
+所有请求通过 Filter 自动注入 `traceId`、`tenantId` 到 MDC,确保日志可按租户和链路筛选。
+
+---
+
+## 4. 分布式追踪(规划)
+
+当前 Batch 9 尚未引入追踪依赖,计划在后续迭代中加入:
+
+| 组件 | 选型 |
+|------|------|
+| Bridge | `micrometer-tracing-bridge-otel` |
+| Exporter | `opentelemetry-exporter-otlp` |
+| Backend | Tempo 或 Jaeger |
+| 采样率 | 生产 10%,开发 100% |
+
+追踪 ID 将自动注入 HTTP 响应头 `X-Trace-Id` 和日志 MDC。
+
+---
+
+## 5. Actuator 端点暴露策略
+
+### 5.1 端口隔离
+
+| 环境 | 业务端口 | 管理端口 | 说明 |
+|------|----------|----------|------|
+| dev | 8080 | 8080(合并) | 开发便利,见 `application-dev.yml` |
+| staging | 8080 | 9090 | 独立端口 |
+| prod | 8080 | 9090 | 独立端口,仅集群内部可达 |
+
+### 5.2 端点白名单
+
+通过 `ActuatorSecurityConfig`(`@Order(1)`)控制:
+
+| 端点 | 路径 | 允许 |
+|------|------|------|
+| health | `/actuator/health` | 允许(含 liveness/readiness 子路径) |
+| info | `/actuator/info` | 允许 |
+| prometheus | `/actuator/prometheus` | 允许 |
+| metrics | `/actuator/metrics` | 允许 |
+| caches | `/actuator/caches` | 允许 |
+| 其他 | `/actuator/**` | 拒绝 |
+
+### 5.3 生产加固建议
+
+在 prod overlay 中,管理端口 9090 不应对外暴露。推荐措施包括:Kubernetes NetworkPolicy 限制仅
+Prometheus ServiceMonitor 和运维 Pod 可访问 9090 端口;或在管理端口上启用 Basic Auth / mTLS。
+
+---
+
+## 6. Dashboard 建议
+
+### 6.1 Grafana Dashboard 布局
+
+建议创建以下面板组:
+
+**Panel Group 1 – 流量与延迟**:QPS(按 status 分组)、P50/P95/P99 延迟、请求成功率。
+
+**Panel Group 2 – JVM 健康**:堆内存使用、GC 暂停时间分布、线程数趋势。
+
+**Panel Group 3 – 数据源**:HikariCP 活跃/空闲/等待连接、超时计数、连接获取时间。
+
+**Panel Group 4 – 缓存**(条件展示):命中率、驱逐率、缓存大小。
+
+**Panel Group 5 – 业务**:`order_created_total` 按 tenantId 分组的速率。
+
+### 6.2 推荐社区 Dashboard
+
+| ID | 名称 | 说明 |
+|----|------|------|
+| 4701 | JVM (Micrometer) | JVM 指标全景 |
+| 11378 | Spring Boot Statistics | HTTP + JVM + Logback |
+| 自定义 | tmpl-api-business | 业务指标(需自建) |
+
+---
+
+## 7. 告警策略
+
+### 7.1 Prometheus Alerting Rules(建议)
+
+```yaml
+groups:
+ - name: tmpl-api-alerts
+ rules:
+ - alert: HighErrorRate
+ expr: >
+ sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m]))
+ /
+ sum(rate(http_server_requests_seconds_count[5m]))
+ > 0.03
+ for: 2m
+ labels:
+ severity: critical
+ annotations:
+ summary: "API 5xx 错误率超过 3%"
+
+ - alert: HighLatencyP95
+ expr: >
+ histogram_quantile(0.95,
+ sum(rate(http_server_requests_seconds_bucket{uri!~"/actuator.*"}[5m])) by (le)
+ ) > 0.5
+ for: 3m
+ labels:
+ severity: warning
+ annotations:
+ summary: "API P95 延迟超过 500ms"
+
+ - alert: DatabaseConnectionExhaustion
+ expr: hikaricp_connections_pending > 5
+ for: 1m
+ labels:
+ severity: critical
+ annotations:
+ summary: "HikariCP 等待连接数过高,可能连接池耗尽"
+
+ - alert: RedisDown
+ expr: up{job="tmpl-api"} == 1 and absent(redis_ping_latency_seconds)
+ for: 1m
+ labels:
+ severity: warning
+ annotations:
+ summary: "Redis 健康检查失败,缓存已降级"
+
+ - alert: HighOrderCreationRate
+ expr: sum(rate(order_created_total[5m])) > 100
+ for: 5m
+ labels:
+ severity: info
+ annotations:
+ summary: "订单创建速率异常偏高,请关注"
+```
+
+### 7.2 告警路由建议
+
+| 严重级别 | 通知渠道 | 响应时间 |
+|----------|----------|----------|
+| critical | PagerDuty / 飞书/钉钉 on-call | 15 分钟内响应 |
+| warning | Slack / 企业微信 | 1 小时内确认 |
+| info | 邮件 / Dashboard 标记 | 下个工作日审查 |
+
+---
+
+## 8. SLO / SLA 定义
+
+### 8.1 服务级别指标 (SLI)
+
+| SLI | 计算方式 | 测量点 |
+|-----|----------|--------|
+| 可用性 | 非 5xx 请求数 / 总请求数 | Prometheus `http_server_requests_seconds_count` |
+| 延迟 | 请求延迟 < 阈值的比例 | Prometheus `http_server_requests_seconds_bucket` |
+| 正确性 | 返回预期结果的请求比例 | 端到端探针(规划中) |
+
+### 8.2 服务级别目标 (SLO)
+
+| SLO | 目标 | 窗口 |
+|-----|------|------|
+| 可用性 | >= 99.9%(即每月允许 ~43 分钟停机或 ~0.1% 错误) | 30 天滚动 |
+| P95 延迟 | < 400ms | 30 天滚动 |
+| P99 延迟 | < 800ms | 30 天滚动 |
+
+### 8.3 错误预算
+
+每月错误预算 = 100% − SLO 目标。以可用性为例,月请求量 1,000,000 时,错误预算为 0.1% × 1,000,000 = 1,000 次 5xx 错误。
+
+当错误预算消耗超过 80% 时,团队应冻结非关键功能发布,优先修复可靠性问题。当错误预算耗尽时,执行错误预算策略(参考 Google SRE Workbook Error Budget Policy)。
+
+### 8.4 SLA(对外承诺,按实际合同定义)
+
+本模板不预设具体 SLA 数字。建议 SLA 宽松于 SLO(例如 SLO 99.9% 时 SLA 设为 99.5%),为内部优化留出空间。SLA 条款应包含排除项(计划维护、不可抗力)和补偿机制。
+
+---
+
+## 9. 健康检查
+
+### 9.1 Kubernetes 探针映射
+
+| 探针 | 路径 | 端口 | 说明 |
+|------|------|------|------|
+| startup | `/actuator/health/liveness` | 9090 | 初始延迟 30s,周期 10s,失败阈值 10 |
+| liveness | `/actuator/health/liveness` | 9090 | 周期 15s,失败阈值 3 |
+| readiness | `/actuator/health/readiness` | 9090 | 周期 10s,失败阈值 3 |
+
+### 9.2 自定义 Health Contributor
+
+| Bean 名 | 检测内容 | 分组 |
+|----------|----------|------|
+| `redisPing` | Redis PING 响应 | readiness |
+| `db`(默认) | DataSource 连接 | readiness |
+| `diskSpace`(默认) | 磁盘空间 | liveness |
\ No newline at end of file
diff --git a/docs/release.md b/docs/release.md
new file mode 100644
index 0000000..425958a
--- /dev/null
+++ b/docs/release.md
@@ -0,0 +1,114 @@
+# 版本发布与回滚 (Release)
+
+---
+
+## 1. 版本号规范
+
+本项目采用 **语义化版本** (Semantic Versioning 2.0.0):`MAJOR.MINOR.PATCH`。
+
+| 组成 | 含义 | 何时递增 |
+|------|------|----------|
+| MAJOR | 不兼容的 API 变更 | 删除端点、修改响应结构、删除字段 |
+| MINOR | 向后兼容的功能新增 | 新增端点、新增可选字段 |
+| PATCH | 向后兼容的缺陷修复 | Bug 修复、安全补丁、文档修正 |
+
+预发布版本可附加标签,如 `1.2.0-rc.1`、`1.2.0-beta.1`。
+
+---
+
+## 2. 发布流程
+
+### 2.1 完整流程
+
+**Step 1 — 更新版本号**
+
+```bash
+# Maven 项目版本
+mvn versions:set -DnewVersion=1.2.0
+mvn versions:commit
+```
+
+**Step 2 — 更新 CHANGELOG.md**
+
+在 `CHANGELOG.md` 顶部添加新版本段落,列出 Added、Changed、Fixed、Removed 各类变更。
+
+**Step 3 — 提交并打 Tag**
+
+```bash
+git add -A
+git commit -m "chore(release): v1.2.0"
+git tag -a v1.2.0 -m "Release v1.2.0"
+git push origin main --tags
+```
+
+**Step 4 — CI 自动构建**
+
+Tag push 触发 CI Pipeline:运行全部测试(`mvn clean verify`);构建 Docker 镜像并推送(`tmpl-api:1.2.0` + `tmpl-api:latest`);生成 GitHub Release(附 CHANGELOG 摘要)。
+
+**Step 5 — 更新 Kustomize 镜像标签**
+
+```bash
+./deploy/scripts/update-image-tag.sh 1.2.0
+git add deploy/overlays//kustomization.yaml
+git commit -m "deploy(): update image tag to 1.2.0"
+git push
+```
+
+GitOps 工具(ArgoCD / Flux)检测到 overlay 变更后自动部署。
+
+### 2.2 热修复流程 (Hotfix)
+
+从对应版本 Tag 创建 `hotfix/1.2.1` 分支 → 修复 → 合并回 `main` → 按上述流程发布 `1.2.1`。
+
+---
+
+## 3. Docker 镜像标签策略
+
+| 标签 | 来源 | 用途 |
+|------|------|------|
+| `1.2.0` | Git Tag `v1.2.0` | 不可变生产标签 |
+| `1.2` | 最新 1.2.x | 方便 staging 滚动 |
+| `latest` | 最新 main 构建 | 仅限 dev 环境 |
+| `sha-abc1234` | Git commit SHA | CI 中间产物追溯 |
+
+生产环境禁止使用 `latest` 标签。
+
+---
+
+## 4. 回滚策略
+
+### 4.1 Kubernetes 回滚
+
+```bash
+# 查看历史
+kubectl rollout history deployment/tmpl-api -n
+
+# 回滚到上一版本
+kubectl rollout undo deployment/tmpl-api -n
+
+# 回滚到指定版本
+kubectl rollout undo deployment/tmpl-api -n --to-revision=3
+```
+
+### 4.2 Kustomize / GitOps 回滚
+
+通过 Git revert overlay 中的镜像标签变更,ArgoCD/Flux 会自动部署回退版本。
+
+```bash
+git revert
+git push
+```
+
+### 4.3 数据库回滚注意事项
+
+Flyway 迁移是 forward-only 的,不支持自动回滚。如果新版本包含 schema 变更,回滚应用时:
+
+若迁移仅添加了列或表(向后兼容),旧版本代码可正常运行,无需回滚 schema。若迁移删除了列或修改了类型(不兼容),必须提前准备反向迁移脚本(新版本号),在回滚应用前先执行。
+
+**建议**:所有 schema 变更设计为向后兼容(expand-then-contract 模式),使回滚成为可能。
+
+---
+
+## 5. 发布检查清单
+
+发布前确认以下项目:`mvn clean verify` 全部通过;CHANGELOG.md 已更新;版本号已在 POM 和 Tag 中同步;Docker 镜像构建成功且大小 <= 200MB;OWASP Dependency-Check 无 CVSS >= 7.0 漏洞;Kustomize overlay 中镜像标签已更新;在 staging 环境完成冒烟测试(健康检查、核心 API、Prometheus 端点)。
\ No newline at end of file
diff --git a/docs/runbook.md b/docs/runbook.md
new file mode 100644
index 0000000..de19395
--- /dev/null
+++ b/docs/runbook.md
@@ -0,0 +1,292 @@
+# 运维排查手册 (Runbook)
+
+本文档覆盖 tmpl-api 服务最常见的故障场景。每个场景包含:触发条件、排查步骤、
+恢复操作和事后改进建议。
+
+> **适用角色**:SRE、运维工程师、值班开发人员。
+> **前置条件**:拥有 Kubernetes 集群 `kubectl` 权限、Grafana/Prometheus 访问权限、
+> 数据库与 Redis CLI 访问权限。
+
+---
+
+## RB-01 数据库连接池耗尽
+
+### 触发条件
+
+告警 `DatabaseConnectionExhaustion` 触发(`hikaricp_connections_pending > 5` 持续 1 分钟)
+或应用日志出现 `HikariPool-1 - Connection is not available, request timed out after 30000ms`。
+
+### 排查步骤
+
+**Step 1 — 确认连接池状态**
+
+```bash
+curl -s http://:9090/actuator/metrics/hikaricp.connections.active | jq .
+curl -s http://:9090/actuator/metrics/hikaricp.connections.pending | jq .
+```
+
+若 `active` 接近 `max`(默认 10)且 `pending` 持续增长,确认连接池耗尽。
+
+**Step 2 — 检查数据库慢查询**
+
+```sql
+SELECT pid, now() - pg_stat_activity.query_start AS duration, query, state
+FROM pg_stat_activity
+WHERE (now() - pg_stat_activity.query_start) > interval '5 seconds'
+ AND state != 'idle'
+ORDER BY duration DESC;
+```
+
+**Step 3 — 检查是否存在长事务**
+
+```sql
+SELECT pid, age(clock_timestamp(), xact_start) AS xact_age, query
+FROM pg_stat_activity
+WHERE xact_start IS NOT NULL
+ORDER BY xact_age DESC
+LIMIT 10;
+```
+
+**Step 4 — 确认数据库最大连接数**
+
+```sql
+SHOW max_connections;
+SELECT count(*) FROM pg_stat_activity;
+```
+
+### 恢复操作
+
+| 严重程度 | 操作 |
+|----------|------|
+| 中等 | 终止长事务:`SELECT pg_terminate_backend();` |
+| 严重 | 重启受影响的 Pod:`kubectl rollout restart deployment/tmpl-api -n ` |
+| 持续 | 提升 HikariCP `maximumPoolSize`(配合数据库 `max_connections` 调整) |
+
+### 事后改进
+
+检查是否有缺少索引的查询导致全表扫描;确认 `@Transactional` 范围是否过大;考虑引入连接池监控 Dashboard 面板和更低的超时阈值。
+
+---
+
+## RB-02 Redis 连接抖动 / 缓存降级
+
+### 触发条件
+
+告警 `RedisDown` 触发,或日志中出现 `GracefulCacheErrorHandler` 的 WARN 级别条目
+(`Cache 'order' get failed`),health 端点显示 `redisPing: DOWN`。
+
+### 排查步骤
+
+**Step 1 — 确认 Redis 实例状态**
+
+```bash
+redis-cli -h -p 6379 PING
+redis-cli -h -p 6379 INFO server | grep uptime
+redis-cli -h -p 6379 INFO clients | grep connected_clients
+```
+
+**Step 2 — 检查网络连通性**
+
+```bash
+kubectl exec -it -- nc -zv 6379
+```
+
+**Step 3 — 检查 Redis 内存与驱逐**
+
+```bash
+redis-cli INFO memory | grep -E "used_memory_human|maxmemory_human|evicted_keys"
+```
+
+**Step 4 — 查看应用日志确认降级行为**
+
+```bash
+kubectl logs --since=5m | grep -i "cache.*fail\|redis\|GracefulCacheErrorHandler"
+```
+
+### 恢复操作
+
+| 场景 | 操作 |
+|------|------|
+| Redis 实例宕机 | 重启 Redis Pod / 故障转移到从节点 |
+| 网络问题 | 检查 NetworkPolicy、Service 配置 |
+| 内存满 | 调整 `maxmemory` 或优化 key TTL |
+| 应用侧连接池满 | 调整 `spring.data.redis.lettuce.pool.max-active` |
+
+> **关键认知**:当 `app.cache.enabled=true` 且 Redis 不可用时,`GracefulCacheErrorHandler`
+> 会记录 WARN 并回退到数据库查询。服务不会宕机,但 DB 负载会上升。若 DB 也接近瓶颈,
+> 应优先恢复 Redis。
+
+### 事后改进
+
+考虑引入 Redis Sentinel / Cluster 高可用方案;评估是否需要 Resilience4j CircuitBreaker 包裹缓存调用;确认缓存 key TTL 设置合理。
+
+---
+
+## RB-03 Flyway 迁移失败
+
+### 触发条件
+
+应用启动失败,日志出现 `FlywayMigrateException` 或 `Migration checksum mismatch`。
+Pod 处于 CrashLoopBackOff 状态。
+
+### 排查步骤
+
+**Step 1 — 查看 Flyway 日志**
+
+```bash
+kubectl logs | grep -i "flyway\|migration"
+```
+
+**Step 2 — 检查迁移历史表**
+
+```sql
+SELECT version, description, success, checksum, installed_on
+FROM flyway_schema_history
+ORDER BY installed_rank DESC
+LIMIT 10;
+```
+
+**Step 3 — 确认失败类型**
+
+| 类型 | 日志关键词 | 说明 |
+|------|------------|------|
+| Checksum 不匹配 | `checksum mismatch` | 已应用的迁移文件被修改 |
+| SQL 语法错误 | `ERROR: syntax error` | 迁移脚本有 SQL 错误 |
+| 冲突版本号 | `Found more than one migration with version` | 版本号重复 |
+| 手动修改了库 | `Detected resolved migration not applied` | 库结构被手动改动 |
+
+### 恢复操作
+
+| 场景 | 操作 |
+|------|------|
+| SQL 语法错误 | 修复脚本,手动清理 `flyway_schema_history` 中失败记录:`DELETE FROM flyway_schema_history WHERE success = false;` |
+| Checksum 不匹配 | **切勿修改已部署的迁移文件!** 使用 `flyway repair` 仅在确认数据一致时更新 checksum |
+| 手动修改了库 | 创建补偿迁移脚本(新版本号),使 schema 回到预期状态 |
+
+> **重要**:Flyway 迁移本质上是不可逆的(forward-only)。每次修复后必须创建新的迁移脚本
+> 而非修改历史脚本。回滚需通过编写反向 DDL 的新迁移实现。
+
+### 事后改进
+
+所有迁移脚本必须在 CI 中通过 Testcontainers 验证;禁止在 `main` 分支合并后修改迁移文件;命名规范参见 [database.md](database.md)。
+
+---
+
+## RB-04 P95 延迟飙升
+
+### 触发条件
+
+告警 `HighLatencyP95` 触发(P95 > 500ms 持续 3 分钟),或 Grafana 延迟面板出现
+突增尖峰。
+
+### 排查步骤
+
+**Step 1 — 定位慢端点**
+
+在 Prometheus 中查询:
+
+```promql
+topk(5,
+ histogram_quantile(0.95,
+ sum(rate(http_server_requests_seconds_bucket[5m])) by (le, uri)
+ )
+)
+```
+
+**Step 2 — 排除基础设施因素**
+
+检查 HikariCP 等待连接数(→ RB-01)、Redis 延迟(→ RB-02)、Pod CPU/内存使用率、
+GC 暂停时间:
+
+```promql
+rate(jvm_gc_pause_seconds_sum[5m]) / rate(jvm_gc_pause_seconds_count[5m])
+```
+
+**Step 3 — 检查上游依赖**
+
+若服务调用外部 API 或消息队列,检查对应 Timer 指标或日志中的耗时。
+
+**Step 4 — 检查缓存命中率**
+
+```promql
+sum(rate(cache_gets_total{result="hit"}[5m]))
+/
+sum(rate(cache_gets_total[5m]))
+```
+
+命中率骤降可能导致大量 DB 查询,引发延迟升高。
+
+### 恢复操作
+
+| 根因 | 操作 |
+|------|------|
+| DB 慢查询 | 添加索引、优化 SQL、终止长事务 |
+| GC 抖动 | 检查堆内存配置、排查内存泄漏 |
+| 缓存击穿 | 恢复 Redis、预热缓存 |
+| 流量突增 | HPA 扩容、启用限流 |
+
+### 事后改进
+
+设置 P99 延迟告警;在慢查询日志中启用 `log_min_duration_statement`;考虑在 API 层引入超时和熔断。
+
+---
+
+## RB-05 容器反复重启 (CrashLoopBackOff)
+
+### 触发条件
+
+`kubectl get pods` 显示 Pod 状态为 `CrashLoopBackOff`,Restart count 持续增加。
+
+### 排查步骤
+
+**Step 1 — 查看 Pod 事件和日志**
+
+```bash
+kubectl describe pod -n
+kubectl logs -n --previous
+```
+
+**Step 2 — 分类退出原因**
+
+| Exit Code | 含义 | 常见原因 |
+|-----------|------|----------|
+| 0 | 正常退出 | 主线程结束(配置错误,未启动 Web 服务器) |
+| 1 | 应用异常 | Bean 创建失败、数据库不可达、Flyway 失败 |
+| 137 | OOMKilled | 容器内存超出 `resources.limits.memory` |
+| 143 | SIGTERM | 被 kubelet 杀死(探针超时) |
+
+**Step 3 — 针对性检查**
+
+若 Exit Code = 137:检查 `resources.limits.memory` 是否过小,JVM `-Xmx` 是否超出容器限制,堆外内存(Metaspace、DirectBuffer)是否泄漏。
+
+若 Exit Code = 1:检查 `application.yml` 配置是否正确,数据库/Redis 连接信息是否正确注入(Secret 挂载),Flyway 是否失败(→ RB-03)。
+
+若 Exit Code = 143:检查 startup/liveness 探针配置,`initialDelaySeconds` 是否足够。
+
+### 恢复操作
+
+| 根因 | 操作 |
+|------|------|
+| OOMKilled | 提升 `resources.limits.memory`,调整 JVM 参数 `-XX:MaxRAMPercentage=75` |
+| DB 不可达 | 确认 Secret 中连接 URL 正确,DB 实例正常,NetworkPolicy 允许流量 |
+| 探针超时 | 延长 `initialDelaySeconds`、`timeoutSeconds` |
+| Flyway 失败 | 参见 RB-03 |
+
+### 事后改进
+
+建立 Pod 资源 baseline(通过 VPA 推荐或历史监控数据);在 CI/CD 中验证配置文件完整性;为 startup 探针留足应用冷启动时间(至少 60s)。
+
+---
+
+## 通用排查工具速查
+
+| 用途 | 命令 |
+|------|------|
+| Pod 日志 | `kubectl logs -n -f --since=10m` |
+| 前一次崩溃日志 | `kubectl logs -n --previous` |
+| 进入 Pod | `kubectl exec -it -n -- /bin/sh` |
+| 端口转发 | `kubectl port-forward 9090:9090 -n ` |
+| 健康检查 | `curl http://localhost:9090/actuator/health | jq .` |
+| Prometheus 指标 | `curl http://localhost:9090/actuator/prometheus` |
+| Redis CLI | `redis-cli -h -p 6379 INFO` |
+| PostgreSQL | `psql -h -U -d -c "SELECT 1;"` |
\ No newline at end of file
diff --git a/docs/security.md b/docs/security.md
new file mode 100644
index 0000000..5f8bf75
--- /dev/null
+++ b/docs/security.md
@@ -0,0 +1,115 @@
+# 安全设计 (Security)
+
+本文档描述 tmpl-api 服务的安全架构、认证授权策略、数据保护措施和合规基线。
+
+---
+
+## 1. 认证与授权
+
+### 1.1 当前状态
+
+当前模板处于 `permitAll()` 状态,SecurityFilterChain 未启用身份验证。这是因为模板
+的首要目标是演示分层架构和多租户设计,认证机制在实际项目中根据需求集成。
+
+### 1.2 规划路线图
+
+| 阶段 | 方案 | 说明 |
+|------|------|------|
+| Phase 1(当前) | `permitAll()` | 模板演示阶段 |
+| Phase 2 | JWT Bearer Token(`spring-boot-starter-oauth2-resource-server`) | 服务作为资源服务器验证 JWT |
+| Phase 3 | OAuth2 + RBAC | 结合权限服务,细粒度角色控制 |
+
+### 1.3 JWT 集成指引(Phase 2 参考)
+
+启用 JWT 后,SecurityConfig 应调整为:要求所有 `/api/**` 请求携带有效的 Bearer Token;Actuator 链保持独立(`@Order(1)`),管理端口不经过 JWT 校验;JWT Claims 中应包含 `tenantId` 字段,用于与 `X-Tenant-Id` Header 交叉校验。
+
+---
+
+## 2. 多租户信任边界
+
+### 2.1 信任模型
+
+```
+[Client] --X-Tenant-Id: 5--> [API Gateway / Auth Layer] --X-Tenant-Id: 5--> [tmpl-api]
+ ↑
+ 验证 tenantId 与 token 一致
+```
+
+**关键原则**:`X-Tenant-Id` Header 不得直接信任来自公网客户端的值。在生产部署中,
+API 网关或认证层必须从 JWT Token / Session 中提取已验证的 tenantId,并覆盖或校验
+请求头中的值。tmpl-api 服务本身假设上游已完成校验。
+
+### 2.2 防护措施
+
+| 层级 | 责任 |
+|------|------|
+| API Gateway | 从 JWT 提取 tenantId,注入 X-Tenant-Id Header,拒绝不匹配的请求 |
+| tmpl-api SecurityFilter | 提取 X-Tenant-Id 并存入 TenantContext (ThreadLocal),缺失时返回 400 |
+| Application Layer | 所有查询自动附加 tenantId 条件(通过 Repository 方法签名) |
+| Database | 建议在关键表上创建 Row-Level Security Policy(PostgreSQL RLS)作为最后防线 |
+
+### 2.3 禁止行为
+
+不得允许一个租户访问另一个租户的数据。Controller 不得接受 URL Path 中的 tenantId 参数覆盖 Header 值。日志中记录 tenantId 以便审计。
+
+---
+
+## 3. Actuator 端点安全
+
+### 3.1 暴露策略
+
+参见 [observability.md](observability.md) 第 5 节。核心原则:
+
+白名单端点(health、info、prometheus、metrics、caches)通过 `ActuatorSecurityConfig`(`@Order(1)`)放行。所有未列入白名单的端点(如 env、beans、heapdump、shutdown)均被拒绝。管理端口在 prod 环境与业务端口分离(9090 vs 8080)。
+
+### 3.2 生产加固
+
+在生产环境中,管理端口 9090 必须通过以下至少一种方式加固:Kubernetes NetworkPolicy 仅允许 Prometheus 和运维 namespace 访问;在反向代理层启用 Basic Auth;启用 mTLS(适用于 Service Mesh 场景)。
+
+---
+
+## 4. 敏感数据处理
+
+### 4.1 传输安全
+
+| 通道 | 要求 |
+|------|------|
+| 客户端 → Gateway | HTTPS (TLS 1.2+) |
+| Gateway → 服务 | 集群内部:明文(依赖 NetworkPolicy)或 mTLS(Service Mesh) |
+| 服务 → 数据库 | `sslmode=require`(生产环境) |
+| 服务 → Redis | 启用 `requirepass` + TLS(如 Redis 6+) |
+
+### 4.2 存储安全
+
+数据库密码、Redis 密码等敏感配置通过 Kubernetes Secret 注入(参见 `deploy/base/secret.yaml`),不存放在 ConfigMap 或代码仓库中。日志中禁止打印完整的数据库连接 URL、密码、或用户敏感数据(如订单金额可以记录,但身份证号、银行卡号不可)。
+
+### 4.3 密钥轮换
+
+建议通过 External Secrets Operator 或 Vault 管理 Secret 的生命周期,实现定期轮换。
+
+---
+
+## 5. 依赖漏洞管理
+
+### 5.1 工具链
+
+| 工具 | 用途 | 频率 |
+|------|------|------|
+| OWASP Dependency-Check | Maven 插件 `dependency-check-maven`,CI 中运行 | 每次构建 |
+| GitHub Dependabot | 自动 PR 升级有漏洞的依赖 | 每日扫描 |
+| SpotBugs + FindSecBugs | 静态代码安全分析 | 每次构建 |
+| Trivy | Docker 镜像漏洞扫描 | CI/CD 镜像构建后 |
+
+### 5.2 漏洞响应流程
+
+发现 CVSS >= 7.0 的漏洞时,24 小时内评估影响并创建修复 Issue;CVSS >= 9.0 立即启动紧急修复。
+
+### 5.3 安全基线检查清单
+
+项目交付前应确认:HTTPS 已启用(或在 Gateway 层终止 TLS);Actuator 非白名单端点不可公网访问;数据库连接使用 Secret 注入;`X-Tenant-Id` 上游已验证;OWASP Dependency-Check 无 CVSS >= 7.0 漏洞;Docker 镜像使用非 root 用户运行(参见 Dockerfile `USER 1001`);Flyway 迁移不含 `DROP` 或 `TRUNCATE`(除非有明确的变更单号)。
+
+---
+
+## 6. 漏洞披露
+
+参见仓库根目录 [SECURITY.md](../SECURITY.md),定义了漏洞报告渠道和响应 SLA。
\ No newline at end of file
diff --git a/tmpl-application/src/main/java/com/ljwx/tmpl/application/order/OrderAppService.java b/tmpl-application/src/main/java/com/ljwx/tmpl/application/order/OrderAppService.java
index d28512d..e9bddc1 100644
--- a/tmpl-application/src/main/java/com/ljwx/tmpl/application/order/OrderAppService.java
+++ b/tmpl-application/src/main/java/com/ljwx/tmpl/application/order/OrderAppService.java
@@ -21,7 +21,8 @@ import java.util.Optional;
* { @code @Cacheable/@CacheEvict} 为 no-op,所有请求直接穿透到数据库。
*
*/
- @Service@---(readOnly = true)
+ @Service
+@Transactional(readOnly = true)
public class OrderAppService {
private final OrderRepository orderRepository;
@@ -32,17 +33,20 @@ public class OrderAppService {
// -------------------- 查询 --------------------
- @---(readOnly = true)
+ @Transactional(readOnly = true)
@Cacheable(cacheNames = "order", key = "#tenantId + '::' + #id", unless = "#result == null")
- public Optional get(Long tenantId, Long id) {
+ public Order getOrder(Long tenantId, Long id) {
if (tenantId == null || tenantId <= 0) {
throw new DomainException(ErrorCode.COMMON_BAD_REQUEST, "tenantId is invalid");
}
- return orderRepository.findByIdAndTenantId(id, tenantId);
+ return orderRepository.findByIdAndTenantId(id, tenantId)
+ .orElseThrow(() -> new DomainException(
+ ErrorCode.COMMON_NOT_FOUND,
+ "Order not found: id=" + id));
}
- @---(readOnly = true)
- public List list(Long tenantId, int pageNum, int pageSize) {
+ @Transactional(readOnly = true)
+ public List listOrders(Long tenantId, int pageNum, int pageSize) {
if (tenantId == null || tenantId <= 0) {
throw new DomainException(ErrorCode.COMMON_BAD_REQUEST, "tenantId is invalid");
}
@@ -51,8 +55,8 @@ public class OrderAppService {
// -------------------- 创建 --------------------
- @---
- public Order create(Long tenantId, Long deptId, String orderNo, BigDecimal amount) {
+ @Transactional
+ public Order createOrder(Long tenantId, Long deptId, String orderNo, BigDecimal amount) {
if (tenantId == null || tenantId <= 0) {
throw new DomainException(ErrorCode.COMMON_BAD_REQUEST, "tenantId is invalid");
}
@@ -65,7 +69,8 @@ public class OrderAppService {
// -------------------- 更新状态 --------------------
- @--- @CacheEvict(cacheNames = "order", key = "#tenantId + '::' + #id")
+ @Transactional
+ @CacheEvict(cacheNames = "order", key = "#tenantId + '::' + #id")
public Order updateStatus(Long tenantId, Long id, String newStatus) {
Order order = orderRepository.findByIdAndTenantId(id, tenantId)
.orElseThrow(() -> new DomainException(
@@ -77,7 +82,8 @@ public class OrderAppService {
// -------------------- 软删除 --------------------
- @--- @CacheEvict(cacheNames = "order", key = "#tenantId + '::' + #id")
+ @Transactional
+ @CacheEvict(cacheNames = "order", key = "#tenantId + '::' + #id")
public void softDelete(Long tenantId, Long id) {
Order order = orderRepository.findByIdAndTenantId(id, tenantId)
.orElseThrow(() -> new DomainException(
diff --git a/tmpl-infrastructure/pom.xml b/tmpl-infrastructure/pom.xml
index e66e4df..85baf1c 100644
--- a/tmpl-infrastructure/pom.xml
+++ b/tmpl-infrastructure/pom.xml
@@ -32,6 +32,12 @@
spring-boot-starter-data-redis
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
org.mapstruct
diff --git a/tmpl-infrastructure/src/main/java/com/ljwx/tmpl/infrastructure/cache/RedisHealthContributor.java b/tmpl-infrastructure/src/main/java/com/ljwx/tmpl/infrastructure/cache/RedisHealthContributor.java
index efdef42..cfb70ad 100644
--- a/tmpl-infrastructure/src/main/java/com/ljwx/tmpl/infrastructure/cache/RedisHealthContributor.java
+++ b/tmpl-infrastructure/src/main/java/com/ljwx/tmpl/infrastructure/cache/RedisHealthContributor.java
@@ -28,7 +28,7 @@ public class RedisHealthContributor implements HealthIndicator {
public Health health() {
try {
String pong = redisTemplate.execute(
- (RedisCallback) connection -> connection.serverCommands().ping());
+ (RedisCallback) connection -> connection.ping());
if ("PONG".equals(pong)) {
return Health.up().withDetail("ping", pong).build();
}
diff --git a/tmpl-infrastructure/src/main/java/com/ljwx/tmpl/infrastructure/persistence/order/OrderDao.java b/tmpl-infrastructure/src/main/java/com/ljwx/tmpl/infrastructure/persistence/order/OrderDao.java
index 9c0a8a3..1950f4a 100644
--- a/tmpl-infrastructure/src/main/java/com/ljwx/tmpl/infrastructure/persistence/order/OrderDao.java
+++ b/tmpl-infrastructure/src/main/java/com/ljwx/tmpl/infrastructure/persistence/order/OrderDao.java
@@ -34,19 +34,19 @@ public class OrderDao implements OrderRepository {
if (order.getId() != null) {
// Update: load managed entity to preserve audit + version
po = jpaRepo.findById(order.getId()).orElseGet(OrderPO::new);
- mapper.updatePOFromDomain(order, po);
+ mapper.updatePO(order, po);
} else {
// Insert: create new PO with business fields only
- po = mapper.toPOFromDomain(order);
+ po = mapper.toPO(order);
}
OrderPO saved = jpaRepo.save(po);
- return mapper.toDomainFromPO(saved);
+ return mapper.toDomain(saved);
}
@Override
public Optional findByIdAndTenantId(Long id, Long tenantId) {
return jpaRepo.findByIdAndTenantId(id, tenantId)
- .map(mapper::toDomainFromPO);
+ .map(mapper::toDomain);
}
@Override
@@ -55,7 +55,7 @@ public class OrderDao implements OrderRepository {
return jpaRepo.findAllByTenantIdAndDeletedFalse(tenantId, pageable)
.getContent()
.stream()
- .map(mapper::toDomainFromPO)
+ .map(mapper::toDomain)
.toList();
}
}
\ No newline at end of file