chore: add CI pipeline, docs gate, OpenAPI snapshot, PR template, docs skeleton
This commit is contained in:
@@ -1,42 +1,38 @@
|
|||||||
## PR 信息
|
## 变更摘要
|
||||||
|
- [ ] feat
|
||||||
|
- [ ] fix
|
||||||
|
- [ ] refactor
|
||||||
|
- [ ] chore
|
||||||
|
- [ ] docs
|
||||||
|
- [ ] perf
|
||||||
|
- [ ] test
|
||||||
|
|
||||||
### 需求号
|
## 影响模块(可多选)
|
||||||
<!-- 填写关联的需求编号,如 R-001。如无需求关联,写"无"并说明原因。 -->
|
- [ ] module: dict
|
||||||
|
- [ ] module: sysconfig
|
||||||
|
- [ ] module: department
|
||||||
|
- [ ] module: menu
|
||||||
|
- [ ] module: user
|
||||||
|
- [ ] module: role
|
||||||
|
- [ ] module: auth
|
||||||
|
- [ ] module: log
|
||||||
|
- [ ] module: other
|
||||||
|
|
||||||
|
## Doc Impact(必选其一,否则不允许合并)
|
||||||
|
- [ ] docs:updated (已更新文档,列出文件)
|
||||||
|
- [ ] docs:not-needed (不需要文档,写一句理由)
|
||||||
|
- [ ] docs:todo (需要但尚未补齐:禁止合并,除非标记 hotfix)
|
||||||
|
|
||||||
### 契约变更说明
|
### 文档变更清单/理由
|
||||||
<!-- 本次 PR 是否涉及以下契约变更?如有,请详细说明变更内容。 -->
|
- docs files:
|
||||||
- [ ] OpenAPI 接口变更(新增/修改/删除端点、字段、错误码)
|
- `docs/CHANGELOG.md`
|
||||||
- [ ] 数据库 Schema 变更(新增 Flyway migration)
|
- `docs/MODULES/xxx.md`
|
||||||
- [ ] 权限变更(新增/修改 permission 字符串)
|
- `docs/API/overview.md`
|
||||||
- [ ] 租户隔离策略变更
|
- `docs/ADR/xxxx.md`
|
||||||
|
- reason:
|
||||||
|
|
||||||
变更详情:
|
## OpenAPI(如有接口/DTO/权限变更)
|
||||||
|
- [ ] openapi:changed(会影响合约)
|
||||||
|
- [ ] openapi:not-changed
|
||||||
|
|
||||||
|
## 其他说明
|
||||||
### 控制面影响
|
|
||||||
<!-- 本次变更是否影响以下控制面? -->
|
|
||||||
- [ ] 认证/鉴权流程
|
|
||||||
- [ ] 租户隔离逻辑
|
|
||||||
- [ ] 日志/可观测性
|
|
||||||
- [ ] CI Gate 脚本
|
|
||||||
- [ ] 配置项(application.yml / 环境变量)
|
|
||||||
|
|
||||||
影响详情:
|
|
||||||
|
|
||||||
|
|
||||||
### 测试证据
|
|
||||||
<!-- 请列出本次变更对应的测试用例。 -->
|
|
||||||
- [ ] 单元测试通过(./mvnw test)
|
|
||||||
- [ ] 集成测试通过(./mvnw -PIT verify)
|
|
||||||
- [ ] 手动验证(如适用,描述步骤)
|
|
||||||
|
|
||||||
测试详情:
|
|
||||||
|
|
||||||
|
|
||||||
### 自检清单
|
|
||||||
- [ ] 代码遵循分层约定(controller→facade→appservice→domain←infra)
|
|
||||||
- [ ] tenant_id 来自 SecurityContext,未从 DTO/请求参数获取
|
|
||||||
- [ ] @PreAuthorize 使用的权限字符串已在 seed 数据中定义
|
|
||||||
- [ ] Flyway migration 版本号不冲突(MVP: V001-V099)
|
|
||||||
- [ ] 无"省略/TODO/自行补充"的代码提交
|
|
||||||
|
|||||||
+77
-26
@@ -1,51 +1,102 @@
|
|||||||
name: LJWX Platform CI
|
name: ci
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: ["main"]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: ["main"]
|
||||||
|
|
||||||
|
env:
|
||||||
|
JAVA_VERSION: "21"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
gate-all:
|
build-and-gate:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:16-alpine
|
image: postgres:16
|
||||||
env:
|
env:
|
||||||
POSTGRES_DB: ljwx_platform_test
|
POSTGRES_DB: ljwx_platform_test
|
||||||
POSTGRES_USER: test
|
POSTGRES_USER: ljwx
|
||||||
POSTGRES_PASSWORD: test
|
POSTGRES_PASSWORD: ljwx123
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
options: >-
|
options: >-
|
||||||
--health-cmd="pg_isready -U test"
|
--health-cmd "pg_isready -U ljwx"
|
||||||
--health-interval=10s
|
--health-interval 5s
|
||||||
--health-timeout=5s
|
--health-timeout 5s
|
||||||
--health-retries=5
|
--health-retries 10
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout @v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup Java 21
|
- name: Set up JDK
|
||||||
uses: actions/setup-java @v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: "temurin"
|
||||||
java-version: '21'
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
cache: 'maven'
|
cache: "maven"
|
||||||
|
|
||||||
- name: Make scripts executable
|
- name: Make scripts executable
|
||||||
run: |
|
run: chmod +x scripts/ci/*.sh
|
||||||
chmod +x mvnw
|
|
||||||
find scripts/ -name "*.sh" -exec chmod +x {} \;
|
|
||||||
|
|
||||||
- name: Run gate-all
|
# ── Build ──
|
||||||
run: bash scripts/gates/gate-all.sh
|
- name: Compile
|
||||||
|
run: ./mvnw -q -DskipTests package
|
||||||
|
|
||||||
|
# ── Unit Tests ──
|
||||||
|
- name: Unit tests
|
||||||
|
run: ./mvnw -q test
|
||||||
|
|
||||||
|
# ── Integration Tests ──
|
||||||
|
- name: Integration tests
|
||||||
env:
|
env:
|
||||||
DB_URL: jdbc:postgresql://localhost:5432/ljwx_platform_test
|
SPRING_DATASOURCE_URL: jdbc:postgresql://localhost:5432/ljwx_platform_test
|
||||||
DB_USERNAME: test
|
SPRING_DATASOURCE_USERNAME: ljwx
|
||||||
DB_PASSWORD: test
|
SPRING_DATASOURCE_PASSWORD: ljwx123
|
||||||
SPRING_PROFILES_ACTIVE: test
|
run: ./mvnw -q -PIT verify
|
||||||
|
|
||||||
|
# ── Export OpenAPI snapshot ──
|
||||||
|
- name: Export OpenAPI snapshot
|
||||||
|
env:
|
||||||
|
PORT: "18080"
|
||||||
|
PROFILE: "test"
|
||||||
|
SPRING_DATASOURCE_URL: jdbc:postgresql://localhost:5432/ljwx_platform_test
|
||||||
|
SPRING_DATASOURCE_USERNAME: ljwx
|
||||||
|
SPRING_DATASOURCE_PASSWORD: ljwx123
|
||||||
|
run: scripts/ci/export-openapi.sh
|
||||||
|
|
||||||
|
# ── Docs Gate ──
|
||||||
|
- name: Docs gate
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
env:
|
||||||
|
BASE_REF: "origin/main"
|
||||||
|
run: scripts/ci/docs-gate.sh
|
||||||
|
|
||||||
|
# ── OpenAPI Diff Gate ──
|
||||||
|
- name: OpenAPI diff gate
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
env:
|
||||||
|
BASE_REF: "origin/main"
|
||||||
|
run: scripts/ci/openapi-diff.sh
|
||||||
|
|
||||||
|
# ── Ensure snapshot committed ──
|
||||||
|
- name: Ensure OpenAPI snapshot committed
|
||||||
|
run: |
|
||||||
|
if git diff --exit-code docs/API/openapi.snapshot.yaml 2>/dev/null; then
|
||||||
|
echo "[openapi] snapshot clean or not yet generated. PASS."
|
||||||
|
else
|
||||||
|
echo "============================================"
|
||||||
|
echo "[openapi] FAIL: openapi.snapshot.yaml changed during CI but not committed."
|
||||||
|
echo ""
|
||||||
|
echo "Please run locally:"
|
||||||
|
echo " scripts/ci/export-openapi.sh"
|
||||||
|
echo " git add docs/API/openapi.snapshot.yaml"
|
||||||
|
echo " git commit -m 'chore: update openapi snapshot'"
|
||||||
|
echo "============================================"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Breaking Changes
|
||||||
|
|
||||||
|
> Update this file whenever `docs/API/openapi.snapshot.yaml` changes.
|
||||||
|
> CI will reject PRs that change the snapshot without updating this file.
|
||||||
|
|
||||||
|
## Format
|
||||||
|
- YYYY-MM-DD: <describe change> (BREAKING | non-breaking)
|
||||||
|
|
||||||
|
## Log
|
||||||
|
- 2026-02-15: Initial API surface established (non-breaking)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# OpenAPI snapshot placeholder
|
||||||
|
# This file is auto-generated by scripts/ci/export-openapi.sh
|
||||||
|
# Do not edit manually unless export is not yet available.
|
||||||
|
#
|
||||||
|
# To generate:
|
||||||
|
# scripts/ci/export-openapi.sh
|
||||||
|
#
|
||||||
|
# CI will fail if this file changes but is not committed.
|
||||||
|
openapi: "3.0.0"
|
||||||
|
info:
|
||||||
|
title: ljwx-platform API
|
||||||
|
version: 0.0.0-snapshot
|
||||||
|
description: Placeholder - run export-openapi.sh to generate actual spec
|
||||||
|
paths: {}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# API Overview
|
||||||
|
|
||||||
|
Base URL: `/api/v1`
|
||||||
|
|
||||||
|
## Auth
|
||||||
|
| Method | Path | Permission | Description |
|
||||||
|
|--------|------|------------|-------------|
|
||||||
|
| POST | /auth/login | - | 登录 |
|
||||||
|
| POST | /auth/refresh | - | 刷新 Token |
|
||||||
|
| POST | /auth/logout | - | 登出 |
|
||||||
|
|
||||||
|
## Menu
|
||||||
|
| Method | Path | Permission | Description |
|
||||||
|
|--------|------|------------|-------------|
|
||||||
|
| GET | /menus | menu:read | 菜单列表 |
|
||||||
|
| GET | /menus/{id} | menu:read | 菜单详情 |
|
||||||
|
| POST | /menus | menu:write | 创建菜单 |
|
||||||
|
| PUT | /menus/{id} | menu:write | 更新菜单 |
|
||||||
|
| DELETE | /menus/{id} | menu:write | 删除菜单 |
|
||||||
|
| GET | /menus/me | (any auth) | 当前用户菜单树 |
|
||||||
|
|
||||||
|
## Role
|
||||||
|
| Method | Path | Permission | Description |
|
||||||
|
|--------|------|------------|-------------|
|
||||||
|
| GET | /roles | role:read | 角色列表 |
|
||||||
|
| GET | /roles/{id} | role:read | 角色详情 |
|
||||||
|
| POST | /roles | role:write | 创建角色 |
|
||||||
|
| PUT | /roles/{id} | role:write | 更新角色 |
|
||||||
|
| DELETE | /roles/{id} | role:write | 删除角色 |
|
||||||
|
| PUT | /roles/{id}/permissions | role:write | 分配权限 |
|
||||||
|
| PUT | /roles/user-roles | role:write | 分配用户角色 |
|
||||||
|
| GET | /roles/user-roles/{userId} | role:read | 查询用户角色 |
|
||||||
|
|
||||||
|
## Dict
|
||||||
|
| Method | Path | Permission | Description |
|
||||||
|
|--------|------|------------|-------------|
|
||||||
|
| GET | /dict-types | dict:read | 字典类型列表 |
|
||||||
|
| GET | /dict-data | dict:read | 字典数据列表 |
|
||||||
|
|
||||||
|
## SysConfig
|
||||||
|
| Method | Path | Permission | Description |
|
||||||
|
|--------|------|------------|-------------|
|
||||||
|
| GET | /sys-configs | config:read | 配置列表 |
|
||||||
|
| GET | /sys-configs/{id} | config:read | 配置详情 |
|
||||||
|
|
||||||
|
## Department
|
||||||
|
| Method | Path | Permission | Description |
|
||||||
|
|--------|------|------------|-------------|
|
||||||
|
| GET | /departments | dept:read | 部门列表 |
|
||||||
|
| GET | /departments/{id} | dept:read | 部门详情 |
|
||||||
|
| POST | /departments | dept:write | 创建部门 |
|
||||||
|
| PUT | /departments/{id} | dept:write | 更新部门 |
|
||||||
|
| DELETE | /departments/{id} | dept:write | 删除部门 |
|
||||||
|
|
||||||
|
## User (Phase 2 - planned)
|
||||||
|
| Method | Path | Permission | Description |
|
||||||
|
|--------|------|------------|-------------|
|
||||||
|
| GET | /users | user:read | 用户列表 |
|
||||||
|
| GET | /users/{id} | user:read | 用户详情 |
|
||||||
|
| POST | /users | user:write | 创建用户 |
|
||||||
|
| PUT | /users/{id} | user:write | 更新用户 |
|
||||||
|
| DELETE | /users/{id} | user:write | 删除用户 |
|
||||||
|
| GET | /users/me | (any auth) | 当前用户信息 |
|
||||||
|
| PUT | /users/{id}/reset-password | user:write | 重置密码 |
|
||||||
|
| PUT | /users/{id}/status | user:write | 切换状态 |
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
Format based on [Keep a Changelog](https://keepachangelog.com/).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Dict module (V101-V102)
|
||||||
|
- SysConfig module (V103-V104)
|
||||||
|
- Menu module (V107-V109)
|
||||||
|
- User/Role enhancement (V110-V111)
|
||||||
|
- Audit field alignment (V112)
|
||||||
|
- Departments table (V113-V114)
|
||||||
|
- SecurityUtil centralized auth extraction
|
||||||
|
- Global exception handler
|
||||||
|
- Role CRUD with tenant isolation
|
||||||
|
- UserRole assignment with dedup
|
||||||
|
- GET /api/v1/menus/me tree endpoint
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- V111 ADMIN permission binding across all tenants
|
||||||
|
- UserRoleAppService duplicate roleId handling
|
||||||
|
- Menu permission_key consistency for all 6 second-level menus
|
||||||
|
- Bean naming conflicts between legacy and new infrastructure packages
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Standardized audit fields: create_* NOT NULL, update_* nullable, is_deleted SMALLINT DEFAULT 0
|
||||||
|
- Replaced private currentUsername/currentUserId/currentTenantId with SecurityUtil.*
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# <Module Name>
|
||||||
|
|
||||||
|
## 定位
|
||||||
|
一句话说明模块职责。
|
||||||
|
|
||||||
|
## 数据库
|
||||||
|
| 表 | 描述 | 租户级 |
|
||||||
|
|----|------|--------|
|
||||||
|
| xxx | ... | 是/否 |
|
||||||
|
|
||||||
|
## 实体
|
||||||
|
- `XxxModel` → 字段列表
|
||||||
|
|
||||||
|
## 端点
|
||||||
|
| Method | Path | Permission | Description |
|
||||||
|
|--------|------|------------|-------------|
|
||||||
|
| GET | /api/v1/xxx | xxx:read | ... |
|
||||||
|
|
||||||
|
## 权限
|
||||||
|
| Key | 描述 |
|
||||||
|
|-----|------|
|
||||||
|
| xxx:read | 查看 |
|
||||||
|
| xxx:write | 管理 |
|
||||||
|
|
||||||
|
## 迁移脚本
|
||||||
|
| Version | 文件 | 描述 |
|
||||||
|
|---------|------|------|
|
||||||
|
| Vxxx | V...sql | ... |
|
||||||
|
|
||||||
|
## 特殊规则
|
||||||
|
- 规则 1:...
|
||||||
|
- 规则 2:...
|
||||||
|
|
||||||
|
## 依赖
|
||||||
|
- 依赖模块:auth, tenant
|
||||||
|
- 被依赖:user
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# ADR-XXXX: <Title>
|
||||||
|
|
||||||
|
## Status
|
||||||
|
Proposed | Accepted | Deprecated | Superseded by ADR-YYYY
|
||||||
|
|
||||||
|
## Date
|
||||||
|
YYYY-MM-DD
|
||||||
|
|
||||||
|
## Context
|
||||||
|
为什么要做这个决策?当前遇到了什么问题?
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
做了什么决策(明确、具体)。
|
||||||
|
|
||||||
|
## Alternatives Considered
|
||||||
|
| 方案 | 优点 | 缺点 | 结论 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| A | ... | ... | 不采用 |
|
||||||
|
| B | ... | ... | 采用 |
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
- 好处:
|
||||||
|
- 代价:
|
||||||
|
- 风险:
|
||||||
|
|
||||||
|
## References
|
||||||
|
- PR: #xxx
|
||||||
|
- Migration: Vxxx
|
||||||
|
- Related ADR: ADR-xxxx
|
||||||
Executable
+68
@@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
BASE_REF="${BASE_REF:-origin/main}"
|
||||||
|
|
||||||
|
echo "[docs-gate] base ref: ${BASE_REF}"
|
||||||
|
|
||||||
|
# 确保有 base ref
|
||||||
|
git fetch origin main --depth=50 2>/dev/null || true
|
||||||
|
|
||||||
|
# 获取变更文件列表
|
||||||
|
CHANGED="$(git diff --name-only "${BASE_REF}...HEAD" 2>/dev/null || git diff --name-only HEAD~1 || true)"
|
||||||
|
|
||||||
|
if [[ -z "${CHANGED}" ]]; then
|
||||||
|
echo "[docs-gate] no changed files detected. PASS."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[docs-gate] changed files:"
|
||||||
|
echo "${CHANGED}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 1) 判断是否属于"必须带文档"的代码变更
|
||||||
|
NEED_DOC=0
|
||||||
|
echo "${CHANGED}" | grep -E -q \
|
||||||
|
'(controller|facade|dto|security|exception)/.*\.java$|db/migration/.*\.sql$|.*(Controller|Facade|Request|Response)\.java$' \
|
||||||
|
&& NEED_DOC=1 || true
|
||||||
|
|
||||||
|
if [[ "${NEED_DOC}" -eq 0 ]]; then
|
||||||
|
echo "[docs-gate] no doc-required code changes detected. PASS."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[docs-gate] doc-requiring code changes detected."
|
||||||
|
|
||||||
|
# 2) 检查 docs 是否有变化
|
||||||
|
DOC_CHANGED=0
|
||||||
|
echo "${CHANGED}" | grep -E -q \
|
||||||
|
'^docs/CHANGELOG\.md$|^docs/MODULES/|^docs/API/overview\.md$|^docs/API/breaking-changes\.md$|^docs/ADR/|^docs/adr/' \
|
||||||
|
&& DOC_CHANGED=1 || true
|
||||||
|
|
||||||
|
# 3) 检查 openapi snapshot 是否变化
|
||||||
|
OPENAPI_CHANGED=0
|
||||||
|
echo "${CHANGED}" | grep -E -q '^docs/API/openapi\.snapshot\.yaml$' && OPENAPI_CHANGED=1 || true
|
||||||
|
|
||||||
|
if [[ "${DOC_CHANGED}" -eq 1 || "${OPENAPI_CHANGED}" -eq 1 ]]; then
|
||||||
|
echo "[docs-gate] docs or openapi snapshot updated. PASS."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<'EOF2'
|
||||||
|
|
||||||
|
============================================
|
||||||
|
[docs-gate] FAIL
|
||||||
|
|
||||||
|
You changed code that requires docs updates, but no docs were changed.
|
||||||
|
|
||||||
|
Fix options (choose at least one):
|
||||||
|
1) Update docs/CHANGELOG.md (recommended, 1-3 lines)
|
||||||
|
2) Update a module doc: docs/MODULES/<module>.md
|
||||||
|
3) Update docs/API/overview.md or docs/ADR/* (docs/adr/*)
|
||||||
|
4) If API changed, run scripts/ci/export-openapi.sh and commit the snapshot
|
||||||
|
|
||||||
|
============================================
|
||||||
|
|
||||||
|
EOF2
|
||||||
|
|
||||||
|
exit 1
|
||||||
Executable
+77
@@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
OUT="docs/API/openapi.snapshot.yaml"
|
||||||
|
PORT="${PORT:-8080}"
|
||||||
|
PROFILE="${PROFILE:-test}"
|
||||||
|
MAX_WAIT="${MAX_WAIT:-90}"
|
||||||
|
|
||||||
|
echo "[openapi] export to ${OUT}, port=${PORT}, profile=${PROFILE}"
|
||||||
|
|
||||||
|
# 1) 编译(如果 jar 不存在)
|
||||||
|
JAR="$(find ljwx-platform-app/target -name '*.jar' -not -name '*-sources*' -not -name '*-javadoc*' 2>/dev/null | head -n1 || true)"
|
||||||
|
if [[ -z "${JAR}" ]]; then
|
||||||
|
echo "[openapi] jar not found, building..."
|
||||||
|
./mvnw -q -DskipTests package
|
||||||
|
JAR="$(find ljwx-platform-app/target -name '*.jar' -not -name '*-sources*' -not -name '*-javadoc*' | head -n1)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[openapi] using jar: ${JAR}"
|
||||||
|
|
||||||
|
# 2) 后台启动应用
|
||||||
|
nohup java -jar "${JAR}" \
|
||||||
|
--spring.profiles.active="${PROFILE}" \
|
||||||
|
--server.port="${PORT}" \
|
||||||
|
> /tmp/openapi-app.log 2>&1 &
|
||||||
|
APP_PID=$!
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
echo "[openapi] stopping app pid=${APP_PID}"
|
||||||
|
kill "${APP_PID}" 2>/dev/null || true
|
||||||
|
wait "${APP_PID}" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
# 3) 等待应用就绪(先试 actuator,再试 OpenAPI 端点自身)
|
||||||
|
echo "[openapi] waiting for app to start (max ${MAX_WAIT}s)..."
|
||||||
|
READY=0
|
||||||
|
for i in $(seq 1 "${MAX_WAIT}"); do
|
||||||
|
# 优先 actuator
|
||||||
|
if curl -fsS "http://127.0.0.1:${PORT}/actuator/health" >/dev/null 2>&1; then
|
||||||
|
READY=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
# 回退:直接试 OpenAPI 端点
|
||||||
|
if curl -fsS "http://127.0.0.1:${PORT}/v3/api-docs" >/dev/null 2>&1; then
|
||||||
|
READY=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "${READY}" -eq 0 ]]; then
|
||||||
|
echo "[openapi] ERROR: app did not start within ${MAX_WAIT}s"
|
||||||
|
echo "[openapi] last 50 lines of log:"
|
||||||
|
tail -50 /tmp/openapi-app.log
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[openapi] app is ready"
|
||||||
|
|
||||||
|
# 4) 导出 OpenAPI
|
||||||
|
mkdir -p "$(dirname "${OUT}")"
|
||||||
|
|
||||||
|
if curl -fsS "http://127.0.0.1:${PORT}/v3/api-docs.yaml" -o "${OUT}" 2>/dev/null; then
|
||||||
|
echo "[openapi] exported yaml to ${OUT}"
|
||||||
|
elif curl -fsS "http://127.0.0.1:${PORT}/v3/api-docs" -o "${OUT%.yaml}.json" 2>/dev/null; then
|
||||||
|
echo "[openapi] yaml not available, exported json to ${OUT%.yaml}.json"
|
||||||
|
else
|
||||||
|
echo "[openapi] WARNING: could not export OpenAPI. Endpoint may not be configured."
|
||||||
|
echo "[openapi] This is non-fatal. Skipping snapshot update."
|
||||||
|
# 创建一个占位文件,避免后续 git diff 报错
|
||||||
|
if [[ ! -f "${OUT}" ]]; then
|
||||||
|
echo "# OpenAPI snapshot not yet available" > "${OUT}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[openapi] done"
|
||||||
Executable
+44
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SNAP="docs/API/openapi.snapshot.yaml"
|
||||||
|
BREAKING="docs/API/breaking-changes.md"
|
||||||
|
BASE_REF="${BASE_REF:-origin/main}"
|
||||||
|
|
||||||
|
echo "[openapi-diff] checking snapshot changes..."
|
||||||
|
|
||||||
|
git fetch origin main --depth=50 2>/dev/null || true
|
||||||
|
|
||||||
|
# 获取变更文件列表
|
||||||
|
CHANGED="$(git diff --name-only "${BASE_REF}...HEAD" 2>/dev/null || git diff --name-only HEAD~1 || true)"
|
||||||
|
|
||||||
|
# 如果 snapshot 没变化,直接 pass
|
||||||
|
if ! echo "${CHANGED}" | grep -q "^${SNAP}$"; then
|
||||||
|
echo "[openapi-diff] snapshot not changed. PASS."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[openapi-diff] snapshot changed."
|
||||||
|
|
||||||
|
# 要求 breaking-changes.md 也有变化
|
||||||
|
if echo "${CHANGED}" | grep -q "^${BREAKING}$"; then
|
||||||
|
echo "[openapi-diff] breaking-changes.md updated. PASS."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<EOF2
|
||||||
|
|
||||||
|
============================================
|
||||||
|
[openapi-diff] FAIL
|
||||||
|
|
||||||
|
OpenAPI snapshot changed but ${BREAKING} was not updated.
|
||||||
|
|
||||||
|
Please add a short note to ${BREAKING}, e.g.:
|
||||||
|
- $(date +%Y-%m-%d): Added GET /api/v1/users/me endpoint (non-breaking)
|
||||||
|
- $(date +%Y-%m-%d): Removed field xxx from YyyResponse (BREAKING)
|
||||||
|
|
||||||
|
============================================
|
||||||
|
|
||||||
|
EOF2
|
||||||
|
|
||||||
|
exit 1
|
||||||
Reference in New Issue
Block a user