Initial commit

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
2026-02-22 23:16:38 +08:00
co-authored by Qwen-Coder
commit 4d954e3bc6
41 changed files with 3622 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
---
name: code-reviewer
description: "Diff-driven code reviewer for LJWX platform. Reviews ONLY files listed in the prompt against Phase scope and hard rules. Read-only access. Outputs structured findings. Use proactively after implementing a phase."
model: claude-sonnet-4-6
tools:
- Read
- Grep
- Glob
- Bash
---
## Role
You are the Code Reviewer for the LJWX Platform. You have **read-only** access. Never edit or create files.
## Workflow
You receive:
- A list of changed files (from `git diff --name-only HEAD`)
- The Phase number and its allowed scope glob patterns
Execute in order:
### Step 1: Read context
Read `CLAUDE.md` (hard rules) and `spec/01-constraints.md` (constraint detail).
### Step 2: Scope check
Every changed file MUST match at least one scope glob pattern from the Phase.
Files outside scope → **CRITICAL** violation (PATCHES 最小化 rule).
### Step 3: Rule checks per file type
**Java files — Controllers** (`*Controller.java`):
- Every `@(Get|Post|Put|Delete|Patch)Mapping` method must have `@PreAuthorize` within 3 lines above
- Exception: login and refresh endpoints (path contains `/auth/login` or `/auth/refresh`)
- Authority format must be `hasAuthority('resource:action')`, not `hasRole(...)`
**Java files — DTOs / Requests / Responses** (`*DTO.java`, `*Dto.java`, `*Request.java`, `*Response.java`):
- Must NOT contain `tenantId` or `tenant_id` field
**Java files — Module DAG**:
- `ljwx-platform-data/` imports must NOT reference `com.ljwx.platform.security`
- `ljwx-platform-security/` imports must NOT reference `com.ljwx.platform.data`
- `ljwx-platform-core/` imports must NOT reference any other ljwx module
- `ljwx-platform-web/` must depend on security (direct or transitive), not data directly
**POM files**:
- No `${latest.version}` placeholder
- Version numbers must be hard-coded digits
**SQL migration files** (`*/migration/*.sql`):
- Business tables (not `QRTZ_*`): must contain all 7 audit columns:
`tenant_id`, `created_by`, `created_time`, `updated_by`, `updated_time`, `deleted`, `version`
- `QRTZ_*` tables: must NOT contain audit columns (they are Quartz system tables)
- No `IF NOT EXISTS` anywhere in migration files
**TypeScript / Vue files** (`.ts`, `.vue`):
- No `: any` or `as any` type usage
- `tsconfig.json` must have `"strict": true`
**package.json files**:
- All dependency versions must use `~` (tilde), never `^` (caret)
**Environment config files** (`.env*`, `*.ts`, `*.vue`):
- Must use `VITE_APP_BASE_API`, never `VITE_API_BASE_URL` or other variants
**Vue Router**:
- Must use vue-router v5 API patterns
- No deprecated v4-only patterns
### Step 4: Output format
Each finding on its own line:
```
[CRITICAL|WARNING|INFO] <file>:<line> — <rule-id> — <description>
evidence: <the offending line or pattern>
fix: <specific remediation>
```
Rule IDs: `scope-violation`, `no-preauthorize`, `dto-tenant-id`, `dag-violation`,
`no-latest-version`, `audit-columns`, `if-not-exists`, `no-any`, `no-caret`,
`wrong-env-var`, `router-v4-api`
End with exactly one of:
```
REVIEW PASSED
```
or:
```
REVIEW FAILED: <N> critical, <M> warnings
```
+89
View File
@@ -0,0 +1,89 @@
{
"permissions": {
"defaultMode": "dontAsk",
"allow": [
"Read",
"Edit",
"Write",
"Bash(mvn clean compile -f *)",
"Bash(mvn test -f *)",
"Bash(mvn verify -f *)",
"Bash(mvn dependency:tree -f *)",
"Bash(mvn versions:display-dependency-updates -f *)",
"Bash(pnpm install)",
"Bash(pnpm install --frozen-lockfile)",
"Bash(pnpm run *)",
"Bash(pnpm list *)",
"Bash(pnpm exec *)",
"Bash(node --version)",
"Bash(java -version)",
"Bash(java --version)",
"Bash(bash scripts/gates/gate-all.sh)",
"Bash(bash scripts/gates/gate-compile.sh)",
"Bash(bash scripts/gates/gate-integration.sh)",
"Bash(bash scripts/gates/gate-manifest.sh)",
"Bash(bash scripts/gates/gate-contract.sh)",
"Bash(bash scripts/gates/gate-nfr.sh)",
"Bash(bash scripts/preflight/preflight-check.sh)",
"Bash(bash scripts/phase-execute.sh *)",
"Bash(bash scripts/phase-bundle.sh *)",
"Bash(bash scripts/phase-parallel.sh *)",
"Bash(cat *)",
"Bash(grep *)",
"Bash(grep -r *)",
"Bash(grep -rn *)",
"Bash(find *)",
"Bash(mkdir -p *)",
"Bash(chmod +x scripts/*)",
"Bash(wc -l *)",
"Bash(head *)",
"Bash(tail *)",
"Bash(diff *)",
"Bash(git add .)",
"Bash(git add -A)",
"Bash(git commit -m *)",
"Bash(git status)",
"Bash(git status --porcelain)",
"Bash(git diff)",
"Bash(git diff --name-only *)",
"Bash(git diff --cached *)",
"Bash(git diff HEAD *)",
"Bash(git log --oneline *)",
"Bash(git branch *)",
"Bash(git checkout -b *)",
"Bash(git checkout *)",
"Bash(git merge --no-ff *)",
"Bash(git worktree add *)",
"Bash(git worktree remove *)",
"Bash(git worktree list)",
"Bash(docker compose -f docker-compose.yml up -d)",
"Bash(docker compose -f docker-compose.yml down)",
"Bash(docker compose -f docker-compose.yml ps)",
"Bash(docker compose -f docker-compose.yml logs *)",
"Task(backend-builder)",
"Task(frontend-builder)",
"Task(code-reviewer)"
],
"deny": [
"Bash(git push *)",
"Bash(rm -rf *)",
"Bash(rm -r *)",
"Bash(curl *)",
"Bash(wget *)",
"Bash(sudo *)",
"Bash(docker run *)",
"Bash(docker exec *)",
"Bash(npx *)",
"Bash(eval *)",
"Bash(sh -c *)",
"WebSearch"
]
},
"sandbox": {
"enabled": true,
"mode": "auto-allow"
},
"env": {
"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
}
}
+30
View File
@@ -0,0 +1,30 @@
---
name: gate-check
description: Run constraint checks on current phase output to verify LJWX platform rules
disable-model-invocation: true
argument-hint: [optional: phase-number]
---
# Gate 检查
对当前 Phase 的产出执行约束检查:
## 检查项
1. **Caret 检查**: 所有 `package.json` 中是否有 `^`(应全部为 `~`)
2. **Env 变量检查**: 是否存在 `VITE_API_BASE_URL`(应为 `VITE_APP_BASE_API`)
3. **审计字段检查**: Flyway SQL 中的 CREATE TABLE 是否包含 7 列审计字段(Quartz 除外)
4. **DAG 检查**: data 模块是否 import 了 security 包,反之亦然
5. **DTO 检查**: DTO 类中是否出现 tenantId 字段
6. **权限注解检查**: Controller 方法是否都有 @PreAuthorize(login/refresh 除外)
7. **TypeScript 检查**: .ts / .vue 文件中是否有 `: any` 或 `as any`
8. **Flyway 检查**: SQL 中是否有 `IF NOT EXISTS`
9. **Vue Router 检查**: 是否使用了 v4 已废弃的 API
## 执行
```bash
bash scripts/gates/gate-manifest.sh PHASE_MANIFEST.txt
```
对每个不通过的检查项,给出具体文件路径和修复建议。
+21
View File
@@ -0,0 +1,21 @@
---
name: phase-exec
description: Execute a specific phase of LJWX platform development. Use when asked to implement a phase or when the user says phase followed by a number.
argument-hint: [phase-number or range like 0-1]
---
# Phase 执行流程
执行 Phase $ARGUMENTS:
1. 读取 `spec/phase/phase-$ARGUMENTS.md`
2. 按其中的"读取清单"加载所需 spec 文件(仅加载列出的文件和章节)
3. 按 `spec/08-output-rules.md` 的格式生成输出
4. 如果是 Bundle(如 $ARGUMENTS 是 "0-1" 或 "2+3"),分别读取对应的 phase brief,每个 Phase 输出独立的块
5. 输出前自检:
- 所有 NEW FILES 是否完整(无省略标记)
- CLAUDE.md 中的硬规则是否全部满足
- Phase-Local Manifest 中的每个文件是否都已生成
- import 路径是否符合 DAG 依赖规则
- package.json 是否全部使用 ~ 而非 ^
6. 完成后提示用户更新 CLAUDE.md 中的 Current Phase
+70
View File
@@ -0,0 +1,70 @@
---
name: preflight
description: Run preflight self-check to verify all spec files, phase briefs, skills, and agents are properly set up before starting code generation. Use before Phase 0.
disable-model-invocation: false
allowed-tools: Read, Bash, Grep, Glob
---
# Preflight 自检
在开始任何 Phase 之前执行完整的环境和文档自检。
## Step 1: 运行自动化脚本
```bash
bash scripts/preflight/preflight-check.sh
```
如果脚本返回非零退出码,逐项修复 FAIL 项后重新运行。
## Step 2: 语义一致性检查(脚本无法覆盖,需人工智能验证)
读取以下文件并检查:
### 2a. CLAUDE.md 硬规则 vs spec/01-constraints.md
读取两个文件,确认:
- CLAUDE.md 硬规则中提到的每条约束,在 spec/01-constraints.md 中有对应的详细定义
- 没有矛盾(例如 CLAUDE.md 说 "不使用 ROLE_ 前缀" 但 constraints 中写了 ROLE_)
### 2b. CLAUDE.md 版本表 vs spec/06-frontend-config.md 中的 package.json
读取两个文件,确认:
- spec/06-frontend-config.md 中所有 package.json 中的版本号与 CLAUDE.md 版本锁定表一致
- 所有依赖使用 `~` 而非 `^`
### 2c. Phase Brief 的读取清单 vs spec/ 文件实际章节
抽查 phase-00、phase-02、phase-05、phase-12 的"读取清单":
- 列出的 spec 文件是否存在
- 列出的 §章节名是否能在对应文件中找到(模糊匹配即可)
### 2d. spec/03-api.md 路由表 vs spec/01-constraints.md 权限字符串
确认路由表中使用的所有权限字符串(如 user:read, job:execute)都在 RBAC 权限列表中。
## Step 3: 输出报告
输出格式:
```
============================
PREFLIGHT REPORT
============================
## 自动化检查
[粘贴 preflight-check.sh 的输出]
## 语义一致性检查
| 检查项 | 结果 | 备注 |
|--------|------|------|
| 硬规则 vs constraints | ✅/❌ | ... |
| 版本号一致性 | ✅/❌ | ... |
| Phase Brief 引用有效性 | ✅/❌ | ... |
| 路由权限覆盖率 | ✅/❌ | ... |
## 结论
✅ READY — 可以开始 Phase 0
或
❌ NOT READY — 需要修复以下问题:[列表]
```
+183
View File
@@ -0,0 +1,183 @@
# LJWX Platform
企业级全栈脚手架。Java 21 / Spring Boot 3.5 后端 + Vue 3 三端前端(Admin 管理后台 / Mobile uni-app 移动端 / Screen 数据大屏)。
## 工作流程(IMPORTANT — YOU MUST FOLLOW)
### 首次启动(Preflight)
如果 Current Phase 显示"尚未开始",必须先执行 preflight 自检:
1. 运行 `/preflight`
2. 等待全部检查通过
3. 通过后将 Current Phase 更新为 `Phase: 0 (Skeleton) — READY`
4. 然后才能执行 `/phase-exec 0`
### 常规 Phase 执行
1. 确认 Current Phase 编号
2. 读取 `spec/phase/phase-{NN}.md` 获取本阶段任务、读取清单、验收条件
3. **仅**按"读取清单"中列出的文件和章节读取 `spec/` 内容
4. 禁止扫描整个 `spec/` 目录(Phase 19 除外)
5. 按 `spec/08-output-rules.md` 格式输出
6. 完成后告知用户更新 Current Phase
## Current Phase
Phase: 0 (Skeleton) — 尚未开始
## 硬规则(违反任何一篇为 FAIL)
1. **DAG 依赖**: core ← {security, data} ← web ← app。security 和 data 互不依赖。禁止 data import security 的任何类,反之亦然
2. **前端 semver**: 所有 dependencies / devDependencies 仅用 `~`(tilde),禁止 `^`(caret)
3. **审计字段**: 所有业务表(Quartz 除外)必须含 tenant_id, created_by, created_time, updated_by, updated_time, deleted, version 共 7 列,均 NOT NULL + 有 DEFAULT
4. **TypeScript**: 禁止 `any`,tsconfig 开启 `strict: true`
5. **权限注解**: 每个 Controller 方法必须 `@PreAuthorize`(login / refresh 除外),格式 `hasAuthority('resource:action')`,不使用 ROLE_ 前缀
6. **tenant_id**: DTO 中禁止出现,前端禁止传递,后端由 Interceptor 自动注入
7. **环境变量**: 前端统一 `VITE_APP_BASE_API`,禁止 `VITE_API_BASE_URL`
8. **Vue Router**: 必须按 vue-router @5 API 写,禁止按 v4 经验写。参考 https://router.vuejs.org/guide/migration/v4-to-v5
9. **Flyway**: 禁止 `IF NOT EXISTS`
10. **POM 版本**: 禁止 `${latest.version}`,所有版本必须硬编码数字
11. **输出完整性**: NEW FILES 必须输出完整文件内容,禁止 `// ...省略...` 或 `/* same as before */`
12. **PATCHES 最小化**: 仅修改与当前 Phase 直接相关的文件,禁止顺手重构、重新格式化、批量重写无关文件
13. **日志脱敏**: password → `***`,phone → 中间四位 `*`,idCard → 中间段 `*`
14. **BCrypt**: admin 密码 `Admin@12345` 使用 cost=10 的 BCrypt hash,写在 V006 种子 SQL 中
## 版本锁定(SINGLE SOURCE OF TRUTH — 其他文件禁止重复写版本号,引用本段即可)
### 后端
| 依赖 | 版本 |
|------|------|
| Java JDK | 21 (21.0.10) |
| Spring Boot | 3.5.11 |
| MyBatis Spring Boot Starter | 3.0.5 |
| springdoc-openapi (BOM) | 2.8.15 |
| Testcontainers (BOM) | 1.21.4 |
| PostgreSQL (Docker) | 16.12 (postgres:16.12-alpine) |
| Flyway | 由 Spring Boot BOM 管理 |
| Quartz | 由 spring-boot-starter-quartz 管理 |
| Maven Wrapper | 3.9.9 |
### 前端
| 依赖 | 版本 | 策略 |
|------|------|------|
| Node.js | 22.22.0 (.nvmrc) | ≥20.19 \| ≥22.12 |
| pnpm | 10.30.1 | packageManager 字段锁定 |
| Vue | ~3.5.28 | ~ patch only |
| Vite | ~7.3.1 | ~ patch only |
| TypeScript | ~5.9.3 | ~ patch only |
| Vue Router | ~5.0.2 | ~ patch only |
| Pinia | ~3.0.4 | ~ patch only |
| Element Plus | ~2.13.2 | ~ patch only |
| @element-plus/icons-vue | ~2.3.2 | ~ patch only |
| ECharts | ~6.0.0 | ~ patch only |
| Axios | ~1.13.5 | ~ patch only |
| @vueuse/core | ~14.2.1 | ~ patch only |
| unplugin-auto-import | ~21.0.0 | ~ devDep |
| unplugin-vue-components | ~31.0.0 | ~ devDep |
| vue-tsc | ~3.2.4 | ~ devDep |
| sass | ~1.97.3 | ~ devDep |
| nprogress | ~0.2.0 | ~ |
| dayjs | ~1.11.19 | ~ |
| @kjgl77/datav-vue3 | ~1.7.4 | ~ Screen 专用 |
| tsup | ~8.5.0 | ~ shared 包构建 |
### Maven 坐标
```xml
<groupId>com.ljwx.platform</groupId>
<artifactId>ljwx-platform-{module}</artifactId>
<version>1.0.0-SNAPSHOT</version>
```
## 代码风格参考(少样本)
### Controller(后端)
```java
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final UserAppService userAppService;
@GetMapping
@PreAuthorize("hasAuthority('user:read')")
public Result<PageResult<UserVO>> list(UserQueryDTO query) {
return Result.ok(userAppService.listUsers(query));
}
@PostMapping
@PreAuthorize("hasAuthority('user:write')")
public Result<Long> create(@RequestBody @Valid UserCreateDTO dto) {
return Result.ok(userAppService.createUser(dto));
}
}
```
### Service(后端)
```java
@Service
@RequiredArgsConstructor
public class UserAppService {
private final UserMapper userMapper;
public PageResult<UserVO> listUsers(UserQueryDTO query) {
// 不传 tenantId,TenantLineInterceptor 自动注入
List<UserVO> list = userMapper.selectUserList(query);
long total = userMapper.countUsers(query);
return new PageResult<>(list, total);
}
}
```
### Vue 组件(前端)
```vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { getUsers } from '@/api/user'
import type { UserVO } from '@ljwx/shared'
const loading = ref(false)
const users = ref<UserVO[]>([])
onMounted(async () => {
loading.value = true
try {
users.value = await getUsers()
} finally {
loading.value = false
}
})
</script>
```
### API 调用(前端)
```typescript
import request from '@/api/request'
import type { Result, PageResult, UserVO, UserQueryDTO } from '@ljwx/shared'
export function getUsers(params?: UserQueryDTO): Promise<PageResult<UserVO>> {
return request.get('/users', { params })
}
```
## 反模式(IMPORTANT — 禁止)
- ❌ data 模块 import com.ljwx.platform.security.* → 违反 DAG
- ❌ security 模块 import com.ljwx.platform.data.* → 违反 DAG
- ❌ DTO 中 private Long tenantId → 违反多租户规则
- ❌ @PreAuthorize("hasRole('ADMIN')") → 不使用 ROLE_ 前缀
- ❌ "axios": "^1.13.5" → 禁止 caret
- ❌ const data: any = response → 禁止 any
- ❌ CREATE TABLE IF NOT EXISTS 在 Flyway SQL 中 → 禁止
- ❌ VITE_API_BASE_URL → 已废弃变量名
- ❌ 按 vue-router v4 经验写 createRouter → 必须用 v5 API
- ❌ 修改非本 Phase 职责范围内的文件 → PATCHES 最小化
- ❌ // ... 省略 ... 或 /* rest same as before */ → 必须输出完整内容
## Compact 指令
When compacting, always preserve: 当前 Phase 编号及进度、已完成的文件清单、硬规则全文、版本锁定表全文、反模式清单。
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/env bash
# Parse YAML front-matter from a Phase Brief file.
# Usage: source scripts/lib/parse-phase-brief.sh <phase-brief-path>
# Exports: PHASE_NUM, PHASE_TITLE, TARGET_BACKEND, TARGET_FRONTEND, PHASE_SCOPE (array)
_BRIEF_PATH="${1:?Usage: source parse-phase-brief.sh <path-to-phase-XX.md>}"
if [[ ! -f "$_BRIEF_PATH" ]]; then
echo "ERROR: Phase brief not found: $_BRIEF_PATH" >&2
return 1 2>/dev/null || exit 1
fi
# Extract YAML block between first pair of --- markers (skip first line, stop before second ---)
_YAML=$(awk '/^---$/{n++;if(n==1){next};if(n==2){exit}} n==1{print}' "$_BRIEF_PATH")
if [[ -z "$_YAML" ]]; then
echo "ERROR: No YAML front-matter found in $_BRIEF_PATH" >&2
echo " Every phase brief must begin with a --- YAML block." >&2
return 1 2>/dev/null || exit 1
fi
PHASE_NUM=$(echo "$_YAML" | grep '^phase:' | awk '{print $2}')
PHASE_TITLE=$(echo "$_YAML" | grep '^title:' | sed 's/^title:[[:space:]]*//' | sed 's/^"//' | sed 's/"$//')
TARGET_BACKEND=$(echo "$_YAML" | grep 'backend:' | awk '{print $2}' | head -1)
TARGET_FRONTEND=$(echo "$_YAML" | grep 'frontend:' | awk '{print $2}' | head -1)
# Parse scope list into array — use sed instead of BASH_REMATCH for portability
PHASE_SCOPE=()
_in_scope=false
while IFS= read -r _line; do
if echo "$_line" | grep -q '^scope:'; then
_in_scope=true
continue
fi
if $_in_scope; then
if echo "$_line" | grep -qE '^[[:space:]]*-[[:space:]]+'; then
_entry=$(echo "$_line" | sed 's/^[[:space:]]*-[[:space:]]*//')
# Strip surrounding quotes if present
_entry="${_entry%\"}"
_entry="${_entry#\"}"
PHASE_SCOPE+=("$_entry")
else
_in_scope=false
fi
fi
done <<< "$_YAML"
export PHASE_NUM PHASE_TITLE TARGET_BACKEND TARGET_FRONTEND PHASE_SCOPE
+108
View File
@@ -0,0 +1,108 @@
#!/usr/bin/env bash
set -euo pipefail
PHASE_NUM="${1:?Usage: phase-execute.sh <phase-number> [--skip-preflight]}"
SKIP_PREFLIGHT="${2:-}"
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$PROJECT_ROOT"
PHASE_BRIEF="spec/phase/phase-$(printf '%02d' "$PHASE_NUM").md"
if [[ ! -f "$PHASE_BRIEF" ]]; then
echo "ERROR: $PHASE_BRIEF not found"
exit 1
fi
# ── Parse Phase Brief ─────────────────────────────────────
source scripts/lib/parse-phase-brief.sh "$PHASE_BRIEF"
echo "Phase $PHASE_NUM: $PHASE_TITLE"
echo " Backend: $TARGET_BACKEND | Frontend: $TARGET_FRONTEND"
echo " Scope: ${PHASE_SCOPE[*]}"
echo ""
# ── Step 0: Preflight ─────────────────────────────────────
if [[ "$SKIP_PREFLIGHT" != "--skip-preflight" ]]; then
echo "═══ STEP 0: Preflight Check ═══"
bash scripts/preflight/preflight-check.sh
echo ""
fi
# ── Step 1a: Backend generation ───────────────────────────
if [[ "$TARGET_BACKEND" == "true" ]]; then
echo "═══ STEP 1a: Backend Builder — Phase $PHASE_NUM ═══"
claude -p "You are backend-builder. Execute Phase $PHASE_NUM.
Read CLAUDE.md, spec/01-constraints.md, and $PHASE_BRIEF.
Generate all backend files listed in the Phase Brief scope.
Follow spec/08-output-rules.md for output format.
After generating, run: bash scripts/gates/gate-compile.sh" \
--agent backend-builder \
--output-format json > "/tmp/ljwx-phase-${PHASE_NUM}-backend.json"
echo "Backend generation complete."
echo ""
fi
# ── Step 1b: Frontend generation ──────────────────────────
if [[ "$TARGET_FRONTEND" == "true" ]]; then
echo "═══ STEP 1b: Frontend Builder — Phase $PHASE_NUM ═══"
claude -p "You are frontend-builder. Execute Phase $PHASE_NUM.
Read CLAUDE.md, spec/06-frontend-config.md, and $PHASE_BRIEF.
Generate all frontend files listed in the Phase Brief scope.
Follow spec/08-output-rules.md for output format.
After generating, run: pnpm install && pnpm run type-check" \
--agent frontend-builder \
--output-format json > "/tmp/ljwx-phase-${PHASE_NUM}-frontend.json"
echo "Frontend generation complete."
echo ""
fi
# ── Step 2: Gate check ────────────────────────────────────
echo "═══ STEP 2: Gate Check ═══"
bash scripts/gates/gate-all.sh
GATE_EXIT=$?
if [[ "$GATE_EXIT" -ne 0 ]]; then
echo "GATE FAILED — aborting."
exit 1
fi
echo ""
# ── Step 3: Diff-driven hard rule scan ────────────────────
echo "═══ STEP 3: Diff Review (zero-token) ═══"
bash scripts/review/diff-review.sh "$PHASE_NUM"
REVIEW_EXIT=$?
if [[ "$REVIEW_EXIT" -ne 0 ]]; then
echo "DIFF REVIEW FAILED — aborting."
exit 1
fi
echo ""
# ── Step 4: Semantic review via Agent ─────────────────────
echo "═══ STEP 4: Semantic Review ═══"
CHANGED_FILES=$(git diff --name-only HEAD)
REVIEW_RESULT=$(claude -p "You are code-reviewer. Review ONLY these changed files:
$CHANGED_FILES
Phase $PHASE_NUM scope allows: ${PHASE_SCOPE[*]}
Read CLAUDE.md and spec/01-constraints.md.
For each file, check the review checklist.
Flag any file NOT in the allowed scope as CRITICAL.
Output each finding as:
[CRITICAL|WARNING|INFO] <file>:<line> — <rule-id> — <description> — <evidence>
End with: REVIEW PASSED or REVIEW FAILED: N critical, M warnings." \
--agent code-reviewer \
--output-format text)
echo "$REVIEW_RESULT"
echo ""
if echo "$REVIEW_RESULT" | grep -q "REVIEW FAILED"; then
exit 1
fi
# ── Step 5: Commit ────────────────────────────────────────
echo "═══ STEP 5: Git Commit ═══"
git add -A
git commit -m "feat(phase-${PHASE_NUM}): ${PHASE_TITLE} — auto-verified"
echo ""
echo "════════════════════════════════════════"
echo "Phase $PHASE_NUM COMPLETE: $PHASE_TITLE"
echo "════════════════════════════════════════"
+161
View File
@@ -0,0 +1,161 @@
#!/usr/bin/env bash
set -euo pipefail
PHASE_NUM="${1:?Usage: phase-parallel.sh <phase-number>}"
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$PROJECT_ROOT"
PHASE_BRIEF="spec/phase/phase-$(printf '%02d' "$PHASE_NUM").md"
source scripts/lib/parse-phase-brief.sh "$PHASE_BRIEF"
if [[ "$TARGET_BACKEND" != "true" || "$TARGET_FRONTEND" != "true" ]]; then
echo "Phase $PHASE_NUM does not require both backend and frontend."
echo " Backend: $TARGET_BACKEND | Frontend: $TARGET_FRONTEND"
echo "Use scripts/phase-execute.sh instead."
exit 1
fi
MAIN_BRANCH=$(git branch --show-current)
BE_BRANCH="phase-${PHASE_NUM}-backend"
FE_BRANCH="phase-${PHASE_NUM}-frontend"
BE_WORKTREE="/tmp/ljwx-worktree-be-${PHASE_NUM}"
FE_WORKTREE="/tmp/ljwx-worktree-fe-${PHASE_NUM}"
# ── Cleanup on exit ───────────────────────────────────────
cleanup() {
echo ""
echo "Cleaning up worktrees..."
git worktree remove "$BE_WORKTREE" --force 2>/dev/null || true
git worktree remove "$FE_WORKTREE" --force 2>/dev/null || true
git branch -D "$BE_BRANCH" 2>/dev/null || true
git branch -D "$FE_BRANCH" 2>/dev/null || true
}
trap cleanup EXIT
# ── Step 0: Preflight ─────────────────────────────────────
echo "═══ Preflight ═══"
bash scripts/preflight/preflight-check.sh
echo ""
# ── Step 1: Create isolated worktrees ─────────────────────
echo "═══ Creating worktrees ═══"
git branch "$BE_BRANCH" HEAD
git branch "$FE_BRANCH" HEAD
git worktree add "$BE_WORKTREE" "$BE_BRANCH"
git worktree add "$FE_WORKTREE" "$FE_BRANCH"
echo " Backend worktree: $BE_WORKTREE ($BE_BRANCH)"
echo " Frontend worktree: $FE_WORKTREE ($FE_BRANCH)"
echo ""
# ── Step 2: Parallel generation ───────────────────────────
echo "═══ Parallel Generation — Phase $PHASE_NUM ═══"
(
cd "$BE_WORKTREE"
echo "[BE] Starting backend generation..."
claude -p "You are backend-builder. Execute Phase $PHASE_NUM backend tasks.
Read CLAUDE.md, spec/01-constraints.md, spec/08-output-rules.md, and $PHASE_BRIEF.
Generate ONLY backend files (.java, pom.xml, .xml, .yml, .sql, .properties).
Do NOT generate any frontend files (.vue, .ts, .json for node, .css, .html).
Follow spec/08-output-rules.md for output format." \
--agent backend-builder \
--output-format text
git add -A
git commit -m "wip: phase-${PHASE_NUM} backend" --allow-empty
echo "[BE] Done."
) &
PID_BE=$!
(
cd "$FE_WORKTREE"
echo "[FE] Starting frontend generation..."
claude -p "You are frontend-builder. Execute Phase $PHASE_NUM frontend tasks.
Read CLAUDE.md, spec/06-frontend-config.md, spec/08-output-rules.md, and $PHASE_BRIEF.
Generate ONLY frontend files (.vue, .ts, package.json, .scss, .html, tsconfig*.json).
Do NOT generate any backend files (.java, pom.xml, .sql, .yml backend configs).
Follow spec/08-output-rules.md for output format." \
--agent frontend-builder \
--output-format text
git add -A
git commit -m "wip: phase-${PHASE_NUM} frontend" --allow-empty
echo "[FE] Done."
) &
PID_FE=$!
echo "Backend PID: $PID_BE | Frontend PID: $PID_FE"
echo "Waiting for both agents to complete..."
BE_EXIT=0
FE_EXIT=0
wait $PID_BE || BE_EXIT=$?
wait $PID_FE || FE_EXIT=$?
if [[ "$BE_EXIT" -ne 0 ]]; then
echo "ERROR: Backend generation failed (exit $BE_EXIT)"
exit 1
fi
if [[ "$FE_EXIT" -ne 0 ]]; then
echo "ERROR: Frontend generation failed (exit $FE_EXIT)"
exit 1
fi
echo "Both agents completed successfully."
echo ""
# ── Step 3: Merge both branches back ─────────────────────
echo "═══ Merging worktrees → $MAIN_BRANCH ═══"
git merge --no-ff "$BE_BRANCH" -m "merge: phase-${PHASE_NUM} backend"
git merge --no-ff "$FE_BRANCH" -m "merge: phase-${PHASE_NUM} frontend"
echo ""
# ── Step 4: Gate check on merged result ───────────────────
echo "═══ Gate Check (merged) ═══"
bash scripts/gates/gate-all.sh
GATE_EXIT=$?
if [[ "$GATE_EXIT" -ne 0 ]]; then
echo "GATE FAILED — aborting."
exit 1
fi
echo ""
# ── Step 5: Diff review on merged result ──────────────────
echo "═══ Diff Review (merged) ═══"
bash scripts/review/diff-review.sh "$PHASE_NUM"
DREVIEW_EXIT=$?
if [[ "$DREVIEW_EXIT" -ne 0 ]]; then
echo "DIFF REVIEW FAILED — aborting."
exit 1
fi
echo ""
# ── Step 6: Semantic review via Agent ─────────────────────
echo "═══ Semantic Review ═══"
CHANGED_FILES=$(git diff --name-only HEAD~2)
REVIEW_RESULT=$(claude -p "You are code-reviewer. Review ONLY these changed files:
$CHANGED_FILES
Phase $PHASE_NUM scope: ${PHASE_SCOPE[*]}
Read CLAUDE.md and spec/01-constraints.md.
Check all hard rules against each changed file.
Flag any file outside scope as CRITICAL (scope-violation).
Output structured findings. End with REVIEW PASSED or REVIEW FAILED: N critical, M warnings." \
--agent code-reviewer \
--output-format text)
echo "$REVIEW_RESULT"
echo ""
if echo "$REVIEW_RESULT" | grep -q "REVIEW FAILED"; then
echo "SEMANTIC REVIEW FAILED — aborting."
exit 1
fi
# ── Step 7: Squash merge commits into one ─────────────────
echo "═══ Final Commit ═══"
git reset --soft HEAD~2
git commit -m "feat(phase-${PHASE_NUM}): ${PHASE_TITLE} — parallel verified"
echo ""
echo "════════════════════════════════════════"
echo "Phase $PHASE_NUM COMPLETE (parallel): $PHASE_TITLE"
echo "════════════════════════════════════════"
+224
View File
@@ -0,0 +1,224 @@
#!/usr/bin/env bash
set -euo pipefail
PASS=0
FAIL=0
WARN=0
pass() { echo " ✅ $1"; PASS=$((PASS + 1)); }
fail() { echo " ❌ $1"; FAIL=$((FAIL + 1)); }
warn() { echo " ⚠️ $1"; WARN=$((WARN + 1)); }
echo "============================================"
echo " LJWX Platform Preflight Check"
echo " Date: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
echo "============================================"
echo ""
# ============================================
# 1. 核心文件存在性检查
# ============================================
echo "[1/8] Core Files"
CORE_FILES=(
"CLAUDE.md"
"spec.md"
"spec/INDEX.md"
"spec/01-constraints.md"
"spec/02-architecture.md"
"spec/03-api.md"
"spec/04-database.md"
"spec/05-backend-config.md"
"spec/06-frontend-config.md"
"spec/07-devops.md"
"spec/08-output-rules.md"
)
for f in "${CORE_FILES[@]}"; do
if [[ -f "$f" ]]; then
# 非空检查
if [[ -s "$f" ]]; then
pass "$f exists and non-empty"
else
fail "$f exists but is EMPTY"
fi
else
fail "$f MISSING"
fi
done
# ============================================
# 2. Phase Brief 文件检查(phase-00 ~ phase-19)
# ============================================
echo ""
echo "[2/8] Phase Brief Files"
for i in $(seq -w 0 19); do
pf="spec/phase/phase-${i}.md"
if [[ -f "$pf" && -s "$pf" ]]; then
pass "$pf"
else
fail "$pf missing or empty"
fi
done
# ============================================
# 3. Claude Code 扩展文件
# ============================================
echo ""
echo "[3/8] Claude Code Extensions"
EXTENSION_FILES=(
".claude/skills/phase-exec/SKILL.md"
".claude/skills/gate-check/SKILL.md"
)
for f in "${EXTENSION_FILES[@]}"; do
if [[ -f "$f" && -s "$f" ]]; then
pass "$f"
else
fail "$f missing or empty"
fi
done
# ============================================
# 4. CLAUDE.md 内容完整性
# ============================================
echo ""
echo "[4/8] CLAUDE.md Content Integrity"
check_claude_md() {
local keyword="$1"
local label="$2"
if grep -qi "$keyword" CLAUDE.md; then
pass "CLAUDE.md contains: $label"
else
fail "CLAUDE.md missing: $label"
fi
}
check_claude_md "Current Phase" "Current Phase 标记"
check_claude_md "硬规则" "硬规则段"
check_claude_md "版本锁定" "版本锁定段"
check_claude_md "反模式" "反模式段"
check_claude_md "Compact 指令" "Compact 指令"
check_claude_md "DAG 依赖" "DAG 依赖规则"
check_claude_md "3.5.11" "Spring Boot 版本"
check_claude_md "22.22.0" "Node.js 版本"
check_claude_md "10.30.1" "pnpm 版本"
check_claude_md "~3.5.28" "Vue 版本(tilde)"
check_claude_md "~7.3.1" "Vite 版本(tilde)"
check_claude_md "Vue Router.*@5" "Vue Router 5 约束"
check_claude_md "VITE_APP_BASE_API" "环境变量名"
check_claude_md "hasAuthority" "权限注解格式"
# ============================================
# 5. spec/INDEX.md 防漂移规则
# ============================================
echo ""
echo "[5/8] INDEX.md Integrity"
if grep -qi "单一事实源" spec/INDEX.md; then
pass "INDEX.md contains 单一事实源 rule"
else
fail "INDEX.md missing 单一事实源 rule"
fi
if grep -qi "防漂移" spec/INDEX.md; then
pass "INDEX.md contains 防漂移 rules"
else
fail "INDEX.md missing 防漂移 rules"
fi
# ============================================
# 6. 版本号唯一性(不应在 spec/ 中重复出现硬编码版本)
# ============================================
echo ""
echo "[6/8] Version Single Source of Truth"
# 检查 spec/ 下是否直接硬编码了 Spring Boot 版本号(应引用 CLAUDE.md)
SB_IN_SPEC=$(grep -rn "3\.5\.11" spec/ 2>/dev/null | grep -v "见 CLAUDE" | grep -v "INDEX" || true)
if [[ -z "$SB_IN_SPEC" ]]; then
pass "Spring Boot version not duplicated in spec/"
else
warn "Spring Boot version (3.5.11) found in spec/ — should reference CLAUDE.md instead"
echo " $SB_IN_SPEC"
fi
# ============================================
# 7. Phase Brief 结构检查(抽查 phase-00)
# ============================================
echo ""
echo "[7/8] Phase Brief Structure (spot check: phase-00)"
PHASE00="spec/phase/phase-00.md"
if [[ -f "$PHASE00" ]]; then
for section in "读取清单" "任务" "Phase-Local Manifest" "验收条件"; do
if grep -qi "$section" "$PHASE00"; then
pass "phase-00 contains section: $section"
else
fail "phase-00 missing section: $section"
fi
done
else
fail "phase-00.md not found, cannot check structure"
fi
# ============================================
# 8. Skill/Agent 格式检查
# ============================================
echo ""
echo "[8/8] Skill/Agent Format"
# Skills 应包含 frontmatter
for skill in .claude/skills/*/SKILL.md; do
[[ ! -f "$skill" ]] && continue
if head -1 "$skill" | grep -q "^---"; then
pass "$skill has YAML frontmatter"
else
fail "$skill missing YAML frontmatter"
fi
if grep -q "description:" "$skill"; then
pass "$skill has description field"
else
fail "$skill missing description field"
fi
done
# Agents 应包含 frontmatter (如果存在)
if compgen -G ".claude/agents/*.md" > /dev/null; then
for agent in .claude/agents/*.md; do
[[ ! -f "$agent" ]] && continue
if head -1 "$agent" | grep -q "^---"; then
pass "$agent has YAML frontmatter"
else
fail "$agent missing YAML frontmatter"
fi
done
fi
# ============================================
# Summary
# ============================================
echo ""
echo "============================================"
echo " PREFLIGHT SUMMARY"
echo "============================================"
echo " ✅ PASS: $PASS"
echo " ❌ FAIL: $FAIL"
echo " ⚠️ WARN: $WARN"
echo ""
if [[ $FAIL -gt 0 ]]; then
echo " 🚫 PREFLIGHT FAILED — Fix $FAIL issue(s) before starting Phase 0"
echo ""
exit 1
else
if [[ $WARN -gt 0 ]]; then
echo " ✅ PREFLIGHT PASSED (with $WARN warning(s))"
else
echo " ✅ PREFLIGHT PASSED — Ready to start Phase 0"
fi
echo ""
exit 0
fi
+235
View File
@@ -0,0 +1,235 @@
#!/usr/bin/env bash
set -euo pipefail
PHASE_NUM="${1:?Usage: diff-review.sh <phase-number>}"
PROJECT_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
cd "$PROJECT_ROOT"
PHASE_BRIEF="spec/phase/phase-$(printf '%02d' "$PHASE_NUM").md"
source scripts/lib/parse-phase-brief.sh "$PHASE_BRIEF"
CHANGED_FILES=$(git diff --name-only HEAD)
ERRORS=0
WARNINGS=0
echo "── Diff-driven Review: Phase $PHASE_NUM ($PHASE_TITLE) ──"
echo ""
# ── 1. Scope check ────────────────────────────────────────
echo "[Scope Check]"
while IFS= read -r file; do
[[ -z "$file" ]] && continue
MATCHED=false
for pattern in "${PHASE_SCOPE[@]}"; do
# Direct match
if [[ "$file" == "$pattern" ]]; then
MATCHED=true
break
fi
# Trailing /** glob: match any file under the directory
if [[ "$pattern" == *"/**" ]]; then
_dir="${pattern%/**}"
if [[ "$file" == "$_dir/"* ]]; then
MATCHED=true
break
fi
fi
# Trailing /* glob: match one level under directory
if [[ "$pattern" == *"/*" ]] && [[ "$pattern" != *"/**" ]]; then
_dir="${pattern%/*}"
_rest="${file#"$_dir/"}"
if [[ "$file" == "$_dir/"* ]] && [[ "$_rest" != *"/"* ]]; then
MATCHED=true
break
fi
fi
done
if ! $MATCHED; then
echo " CRITICAL $file — outside Phase $PHASE_NUM scope"
echo " (allowed: ${PHASE_SCOPE[*]})"
((ERRORS++)) || true
fi
done <<< "$CHANGED_FILES"
echo ""
# ── 2. No caret (^) in package.json ───────────────────────
echo "[Rule: no-caret]"
CARET_HITS=$(grep -rn '"\^' --include='package.json' . 2>/dev/null | grep -v 'node_modules' || true)
if [[ -n "$CARET_HITS" ]]; then
while IFS= read -r hit; do
echo " CRITICAL $hit — caret (^) version found; must use tilde (~)"
((ERRORS++)) || true
done <<< "$CARET_HITS"
else
echo " OK"
fi
echo ""
# ── 3. No `any` type in TypeScript / Vue ──────────────────
echo "[Rule: no-any-type]"
ANY_HITS=$(grep -rn ': any\|as any' --include='*.ts' --include='*.vue' . 2>/dev/null \
| grep -v 'node_modules' \
| grep -v '//.*: any' || true)
if [[ -n "$ANY_HITS" ]]; then
while IFS= read -r hit; do
echo " CRITICAL $hit — TypeScript 'any' type found"
((ERRORS++)) || true
done <<< "$ANY_HITS"
else
echo " OK"
fi
echo ""
# ── 4. Env var name must be VITE_APP_BASE_API ─────────────
echo "[Rule: env-var-consistency]"
BAD_ENV=$(grep -rn 'VITE_API_BASE_URL\|VITE_BASE_API\|VITE_APP_API\b' \
--include='*.ts' --include='*.vue' --include='.env' --include='.env.*' . 2>/dev/null \
| grep -v 'node_modules' || true)
if [[ -n "$BAD_ENV" ]]; then
while IFS= read -r hit; do
echo " CRITICAL $hit — wrong env var name; must be VITE_APP_BASE_API"
((ERRORS++)) || true
done <<< "$BAD_ENV"
else
echo " OK"
fi
echo ""
# ── 5. Flyway: no IF NOT EXISTS ───────────────────────────
echo "[Rule: flyway-no-if-not-exists]"
IF_NOT_HITS=$(grep -rni 'IF NOT EXISTS' --include='*.sql' . 2>/dev/null | grep -v 'node_modules' || true)
if [[ -n "$IF_NOT_HITS" ]]; then
while IFS= read -r hit; do
echo " CRITICAL $hit — Flyway migration must not use IF NOT EXISTS"
((ERRORS++)) || true
done <<< "$IF_NOT_HITS"
else
echo " OK"
fi
echo ""
# ── 6. Business tables must have all 7 audit columns ──────
echo "[Rule: audit-columns]"
AUDIT_ERRORS=0
SQL_FILES=$(find . -name '*.sql' -path '*/migration/*' 2>/dev/null | grep -v 'node_modules' || true)
while IFS= read -r sql_file; do
[[ -z "$sql_file" ]] && continue
# Find each CREATE TABLE statement name
while IFS= read -r table; do
[[ -z "$table" ]] && continue
# Skip Quartz tables
_tbl_lower=$(echo "$table" | tr '[:upper:]' '[:lower:]')
if [[ "$_tbl_lower" == *"qrtz_"* ]]; then
continue
fi
# Extract the table body between the CREATE TABLE and the matching );
TABLE_BLOCK=$(awk "/CREATE TABLE[[:space:]]+${table}[[:space:]]*\(/{found=1} found{print} found && /\);/{exit}" "$sql_file" || true)
for col in tenant_id created_by created_time updated_by updated_time deleted version; do
if ! echo "$TABLE_BLOCK" | grep -qi "[[:space:]]${col}[[:space:]]"; then
echo " CRITICAL $sql_file — table '$table' missing audit column: $col"
((ERRORS++)) || true
((AUDIT_ERRORS++)) || true
fi
done
done < <(grep -oP '(?i)CREATE TABLE\s+\K\S+' "$sql_file" 2>/dev/null || true)
done <<< "$SQL_FILES"
if [[ "$AUDIT_ERRORS" -eq 0 ]]; then
echo " OK"
fi
echo ""
# ── 7. DTO must not expose tenant_id ──────────────────────
echo "[Rule: dto-no-tenant-id]"
DTO_HITS=$(grep -rn 'tenantId\|tenant_id' \
--include='*DTO.java' --include='*Dto.java' \
--include='*Request.java' --include='*Response.java' \
. 2>/dev/null | grep -v 'node_modules' || true)
if [[ -n "$DTO_HITS" ]]; then
while IFS= read -r hit; do
echo " CRITICAL $hit — DTO must not expose tenantId"
((ERRORS++)) || true
done <<< "$DTO_HITS"
else
echo " OK"
fi
echo ""
# ── 8. Controller methods must have @PreAuthorize ─────────
echo "[Rule: controller-preauthorize]"
CTRL_ERRORS=0
CONTROLLER_FILES=$(find . -name '*Controller.java' 2>/dev/null | grep -v 'node_modules' || true)
while IFS= read -r ctrl; do
[[ -z "$ctrl" ]] && continue
while IFS= read -r method_line; do
[[ -z "$method_line" ]] && continue
LINE_NUM=$(echo "$method_line" | cut -d: -f1)
LINE_CONTENT=$(echo "$method_line" | cut -d: -f2-)
# Skip login and refresh endpoints
if echo "$LINE_CONTENT" | grep -q 'login\|refresh'; then
continue
fi
START=$((LINE_NUM > 3 ? LINE_NUM - 3 : 1))
CONTEXT=$(sed -n "${START},${LINE_NUM}p" "$ctrl" 2>/dev/null || true)
if ! echo "$CONTEXT" | grep -q '@PreAuthorize'; then
echo " CRITICAL $ctrl:$LINE_NUM — mapping without @PreAuthorize"
((ERRORS++)) || true
((CTRL_ERRORS++)) || true
fi
done < <(grep -n '@\(Get\|Post\|Put\|Delete\|Patch\)Mapping' "$ctrl" 2>/dev/null || true)
done <<< "$CONTROLLER_FILES"
if [[ "$CTRL_ERRORS" -eq 0 ]]; then
echo " OK"
fi
echo ""
# ── 9. No ${latest.version} in POM ───────────────────────
echo "[Rule: no-latest-version]"
POM_HITS=$(grep -rn '\${latest\.version}' --include='pom.xml' . 2>/dev/null | grep -v 'node_modules' || true)
if [[ -n "$POM_HITS" ]]; then
while IFS= read -r hit; do
echo " CRITICAL $hit — \${latest.version} placeholder found in POM"
((ERRORS++)) || true
done <<< "$POM_HITS"
else
echo " OK"
fi
echo ""
# ── 10. DAG: data must not import security ────────────────
echo "[Rule: dag-data-no-security]"
DAG_HITS=$(grep -rn 'import com\.ljwx\.platform\.security' \
ljwx-platform-data/src/ 2>/dev/null || true)
if [[ -n "$DAG_HITS" ]]; then
while IFS= read -r hit; do
echo " CRITICAL $hit — data module imports security (DAG violation)"
((ERRORS++)) || true
done <<< "$DAG_HITS"
else
echo " OK"
fi
echo ""
# ── 11. DAG: security must not import data ────────────────
echo "[Rule: dag-security-no-data]"
DAG_HITS2=$(grep -rn 'import com\.ljwx\.platform\.data' \
ljwx-platform-security/src/ 2>/dev/null || true)
if [[ -n "$DAG_HITS2" ]]; then
while IFS= read -r hit; do
echo " CRITICAL $hit — security module imports data (DAG violation)"
((ERRORS++)) || true
done <<< "$DAG_HITS2"
else
echo " OK"
fi
echo ""
# ── Summary ───────────────────────────────────────────────
echo "════════════════════════════════════════"
echo " ERRORS: $ERRORS | WARNINGS: $WARNINGS"
if [[ "$ERRORS" -gt 0 ]]; then
echo " DIFF REVIEW: FAILED"
exit 1
else
echo " DIFF REVIEW: PASSED"
exit 0
fi
+8
View File
@@ -0,0 +1,8 @@
# LJWX Platform 规格文档
本文件为入口指引,不包含实际规格内容。
- **AI Agent**: 请读取 `CLAUDE.md`(自动加载),然后按工作流程读取 `spec/phase/phase-{NN}.md`
- **人类开发者**: 请读取 `spec/INDEX.md` 获取完整文档索引
- 完整规格按模块拆分在 `spec/` 目录下
- 版本号的唯一来源是 `CLAUDE.md` 的"版本锁定"段
+83
View File
@@ -0,0 +1,83 @@
# 硬约束
> 所有版本号见 CLAUDE.md "版本锁定"段,本文件不重复。
## 审计字段
所有业务表(Quartz 系统表除外)必须包含以下 7 列:
```sql
tenant_id BIGINT NOT NULL,
created_by BIGINT NOT NULL DEFAULT 0,
created_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_by BIGINT NOT NULL DEFAULT 0,
updated_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted BOOLEAN NOT NULL DEFAULT FALSE,
version INT NOT NULL DEFAULT 1
```
**规则:** NOT NULL + 有 DEFAULT,因此 INSERT 时即使拦截器未触发也不会报错。AuditFieldInterceptor(MyBatis Interceptor)在 INSERT/UPDATE 时从 CurrentUserHolder(core 接口)读取当前用户 ID 写入 created_by / updated_by。
**验收检查:** 每个 Flyway migration 中的 CREATE TABLE(Quartz 表除外)必须包含这 7 列关键字。gate-manifest.sh 中循环检查全部 7 列。
## 多租户行级隔离
所有查询通过 TenantLineInterceptor(MyBatis Interceptor)自动追加 WHERE tenant_id = ?。tenant_id 取自 CurrentTenantHolder.get()(core 接口)。DTO 中禁止出现 tenant_id 字段。前端禁止传递 tenant_id。
## JWT 认证
| 项目 | 值 |
|------|-----|
| Access Token 有效期 | 30 min |
| Refresh Token 有效期 | 7 days |
| 签名算法 | HS256 |
| Payload claims | sub(userId), tenantId, username, type("access"|"refresh"), authorities(权限字符串数组) |
| authorities claim name | authorities(自定义 claim,非 Spring 默认 scope) |
| authorities converter | 自定义 JwtAuthenticationConverter:读取 authorities claim → 转为 SimpleGrantedAuthority,不添加 ROLE_ 前缀 |
| ROLE_ 前缀 | 不使用。RBAC 权限均为 resource:action 格式。`@PreAuthorize("hasAuthority('user:read')")` |
| 密钥配置 | application.yml 中 ljwx.jwt.secret 占位,.env.example 提供示例值 |
**Refresh 流程:** 前端 Axios 拦截 401 → 用 refresh token 调 /api/auth/refresh → 成功则重放原请求 → 失败则跳转登录。
## RBAC 权限
种子数据权限字符串(写入 sys_permission 表):
```
user:read, user:write, user:delete,
role:read, role:write, role:delete,
tenant:read, tenant:write,
job:read, job:write, job:execute,
dict:read, dict:write,
config:read, config:write,
log:read, log:export,
file:read, file:upload, file:delete,
notice:read, notice:write,
screen:read
```
默认 admin 用户拥有全部权限。Controller 方法必须标注 @PreAuthorize。
## Quartz 调度
作业存储 per-tenant 隔离:JobKey(name="{jobId}", group="TENANT_{tenantId}")。Quartz 表使用标准 QRTZ_ 前缀,由 Flyway V010 脚本创建(不使用 IF NOT EXISTS,Quartz 表不含审计字段)。
## 操作日志
异步记录,独立线程池(core size=2, max=4, queue=1024)。日志体超 4096 字节截断。敏感字段脱敏规则:password → ***,phone → 中间四位 *,idCard → 中间段 *。
## 文件管理
Snowflake ID 命名。上传限制 50 MB。白名单后缀:jpg, jpeg, png, gif, webp, svg, pdf, doc, docx, xls, xlsx, ppt, pptx, txt, csv, zip, rar, 7z, mp4, mp3。存储路径 `${ljwx.file.base-path}/tenant_{tenantId}/{yyyy}/{MM}/{dd}/`。
## 缓存策略
字典(dict)和系统配置(config)使用 JVM 本地缓存(Caffeine,由 Spring Boot 管理)。TTL = 10 min。不使用 Redis / MQ / JPA。
## TypeScript 约束
禁止 any 类型。tsconfig.json 开启 strict: true。所有 API 返回值必须有类型定义(放在 packages/shared/src/types/)。
## Vue Router 5 兼容性约束
路由实现必须以 vue-router @~5.0.2 的 API 为准。Vue Router 5 相对 v4 有关键变更(如将 unplugin-vue-router 合并进核心包,路由文件约定发生变化)。以官方文档(https://router.vuejs.org/guide/migration/v4-to-v5)与类型定义为准,禁止按 v4 经验写代码。若某个 v4 API 在 v5 中已被标记 deprecated 或移除,必须使用 v5 推荐的替代方案。
+226
View File
@@ -0,0 +1,226 @@
# 架构
> 版本号见 CLAUDE.md "版本锁定"段。
## 仓库结构
```
ljwx-platform/
├── CLAUDE.md
├── spec.md # stub 入口
├── spec/ # 拆分后的规格文档
├── .claude/ # Claude Code 扩展(skills / agents)
├── pom.xml # Maven parent
├── .mvn/wrapper/ # Maven Wrapper
├── pnpm-workspace.yaml
├── package.json # 前端 root
├── .npmrc
├── .nvmrc # 内容:22.22.0
├── docker-compose.yml
├── .env.example
├── .gitignore
├── .editorconfig
│
├── ljwx-platform-core/ # Java: 核心(接口 + 通用类)
│ ├── pom.xml
│ └── src/main/java/com/ljwx/platform/core/
│ ├── result/ # Result, ErrorCode
│ ├── id/ # SnowflakeIdGenerator
│ ├── entity/ # BaseEntity (审计字段)
│ └── context/ # CurrentUserHolder, CurrentTenantHolder 接口
│
├── ljwx-platform-data/ # Java: 数据层(仅依赖 core)
│ ├── pom.xml
│ └── src/main/java/com/ljwx/platform/data/
│ ├── interceptor/ # AuditFieldInterceptor, TenantLineInterceptor
│ └── config/
│
├── ljwx-platform-security/ # Java: 安全(仅依赖 core)
│ ├── pom.xml
│ └── src/main/java/com/ljwx/platform/security/
│ ├── context/ # SecurityContextUserHolder, SecurityContextTenantHolder
│ ├── jwt/
│ ├── filter/
│ └── config/
│
├── ljwx-platform-web/ # Java: Web 层(依赖 security + core 传递)
│ ├── pom.xml
│ └── src/main/java/com/ljwx/platform/web/
│ ├── advice/ # GlobalExceptionHandler
│ └── config/
│
├── ljwx-platform-app/ # Java: 应用层(聚合 web + data)
│ ├── pom.xml
│ └── src/main/java/com/ljwx/platform/app/
│ ├── LjwxPlatformApplication.java
│ ├── controller/
│ ├── facade/
│ ├── appservice/
│ ├── domain/
│ └── infra/
│
├── packages/
│ └── shared/ # 前端共享 TS 包
│ ├── package.json
│ ├── tsconfig.json
│ └── src/
│ ├── types/
│ ├── constants/
│ └── utils/
│
├── ljwx-platform-admin/ # Vue3 管理后台
│ ├── package.json
│ ├── vite.config.ts
│ ├── tsconfig.json
│ ├── env.d.ts
│ ├── .env.development
│ ├── .env.production
│ ├── index.html
│ └── src/
│ ├── main.ts
│ ├── App.vue
│ ├── router/
│ ├── stores/
│ ├── api/
│ ├── composables/
│ ├── layouts/
│ ├── views/
│ └── styles/
│
├── ljwx-platform-mobile/ # uni-app 移动端
│ ├── package.json
│ ├── manifest.json
│ ├── pages.json
│ └── src/
│
├── ljwx-platform-screen/ # Vue3 数据大屏
│ ├── package.json
│ ├── vite.config.ts
│ └── src/
│
├── docs/
│ ├── adr/ # Architecture Decision Records + 变更日志
│ └── contracts/ # OpenAPI JSON
│
└── scripts/
├── gates/
│ ├── gate-all.sh
│ ├── gate-compile.sh
│ ├── gate-integration.sh
│ ├── gate-contract.sh
│ ├── gate-manifest.sh
│ └── gate-nfr.sh
├── tools/
│ └── export-openapi.sh
└── acceptance/
└── smoke-test.sh
```
## 模块依赖图(DAG — 禁止循环)
```
┌──────────────────┐
│ ljwx-core │ ← 仅接口 + 通用类
└────────▲─────────┘
│
┌────────┴─────────┐
│ │
┌────────┴──────┐ ┌───────┴──────────┐
│ ljwx-security │ │ ljwx-data │
│ (implements │ │ (interceptors │
│ holders from │ │ depend on core │
│ core) │ │ interfaces) │
└────────▲──────┘ └───────▲──────────┘
│ │
└────────┬─────────┘
│
┌────────┴─────────┐
│ ljwx-web │ ← 依赖 security(+ core 传递)
└────────▲─────────┘
│
┌────────┴─────────┐
│ ljwx-app │ ← 聚合:web + data
└──────────────────┘
```
**依赖规则:**
| 模块 | 直接依赖 |
|------|---------|
| ljwx-platform-core | 无(仅外部依赖) |
| ljwx-platform-security | core |
| ljwx-platform-data | core |
| ljwx-platform-web | security(core 经传递依赖获得) |
| ljwx-platform-app | web + data(security 和 core 经传递依赖获得) |
**关键:** security 和 data 互不依赖,仅共同依赖 core 的接口契约。data 模块的 AuditFieldInterceptor 和 TenantLineInterceptor 通过 Spring 依赖注入获取 CurrentUserHolder / CurrentTenantHolder 的实现(由 security 模块在运行时提供)。
## Core 模块关键接口
```java
// com.ljwx.platform.core.context.CurrentUserHolder
public interface CurrentUserHolder {
Long getUserId();
String getUsername();
}
// com.ljwx.platform.core.context.CurrentTenantHolder
public interface CurrentTenantHolder {
Long getTenantId();
}
```
## POM 依赖声明
### ljwx-platform-security/pom.xml
```xml
<dependencies>
<dependency>
<groupId>com.ljwx.platform</groupId>
<artifactId>ljwx-platform-core</artifactId>
</dependency>
<!-- Spring Security, JWT libs, etc. -->
</dependencies>
```
### ljwx-platform-data/pom.xml
```xml
<dependencies>
<dependency>
<groupId>com.ljwx.platform</groupId>
<artifactId>ljwx-platform-core</artifactId>
</dependency>
<!-- MyBatis, PostgreSQL driver, etc. -->
</dependencies>
```
### ljwx-platform-web/pom.xml
```xml
<dependencies>
<dependency>
<groupId>com.ljwx.platform</groupId>
<artifactId>ljwx-platform-security</artifactId>
<!-- core comes transitively -->
</dependency>
</dependencies>
```
### ljwx-platform-app/pom.xml
```xml
<dependencies>
<dependency>
<groupId>com.ljwx.platform</groupId>
<artifactId>ljwx-platform-web</artifactId>
<!-- security + core come transitively -->
</dependency>
<dependency>
<groupId>com.ljwx.platform</groupId>
<artifactId>ljwx-platform-data</artifactId>
<!-- core comes transitively -->
</dependency>
</dependencies>
```
+124
View File
@@ -0,0 +1,124 @@
# API 设计
## 统一响应
```json
{
"code": 200,
"message": "success",
"data": {},
"traceId": "uuid"
}
```
## 错误码(整数枚举)
| 码 | 含义 |
|-----|------|
| 200 | 成功 |
| 400001 | 参数校验失败 |
| 401001 | Token 无效 |
| 401002 | Token 过期 |
| 403001 | 租户拒绝 |
| 403002 | 权限不足 |
| 404001 | 资源不存在 |
| 500001 | 系统内部错误 |
## 路由表
### Auth
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| POST | /api/auth/login | 匿名 | 登录 |
| POST | /api/auth/refresh | 匿名 | 刷新 Token |
| POST | /api/auth/logout | 已认证 | 登出 |
### Users
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | /api/users | user:read | 用户列表 |
| POST | /api/users | user:write | 创建用户 |
| PUT | /api/users/{id} | user:write | 更新用户 |
| DELETE | /api/users/{id} | user:delete | 删除用户 |
### Roles & Permissions
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | /api/roles | role:read | 角色列表 |
| POST | /api/roles | role:write | 创建角色 |
| PUT | /api/roles/{id} | role:write | 更新角色 |
| DELETE | /api/roles/{id} | role:delete | 删除角色 |
| GET | /api/permissions | role:read | 权限列表 |
### Tenants
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | /api/tenants | tenant:read | 租户列表 |
| POST | /api/tenants | tenant:write | 创建租户 |
| PUT | /api/tenants/{id} | tenant:write | 更新租户 |
### Jobs
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | /api/jobs | job:read | 定时任务列表 |
| POST | /api/jobs | job:write | 创建任务 |
| PUT | /api/jobs/{id} | job:write | 更新任务 |
| POST | /api/jobs/{id}/execute | job:execute | 立即执行 |
| POST | /api/jobs/{id}/pause | job:write | 暂停 |
| POST | /api/jobs/{id}/resume | job:write | 恢复 |
### Dicts
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | /api/dicts | dict:read | 字典列表 |
| POST | /api/dicts | dict:write | 创建字典 |
| PUT | /api/dicts/{id} | dict:write | 更新字典 |
| GET | /api/dicts/type/{type} | dict:read | 按类型查字典项 |
### Configs
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | /api/configs | config:read | 配置列表 |
| POST | /api/configs | config:write | 创建配置 |
| PUT | /api/configs/{id} | config:write | 更新配置 |
| GET | /api/configs/key/{key} | config:read | 按 key 查配置 |
### Logs
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | /api/logs/operation | log:read | 操作日志列表 |
| GET | /api/logs/login | log:read | 登录日志列表 |
| POST | /api/logs/export | log:export | 导出日志 |
### Files
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | /api/files | file:read | 文件列表 |
| POST | /api/files/upload | file:upload | 上传文件 |
| DELETE | /api/files/{id} | file:delete | 删除文件 |
| GET | /api/files/{id}/download | file:read | 下载文件 |
### Notices
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | /api/notices | notice:read | 通知列表 |
| POST | /api/notices | notice:write | 发布通知 |
| PUT | /api/notices/{id} | notice:write | 更新通知 |
### Screen
| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | /api/screen/overview | screen:read | 大屏概览数据 |
| GET | /api/screen/realtime | screen:read | 大屏实时数据 |
| GET | /api/screen/trend | screen:read | 大屏趋势数据 |
+35
View File
@@ -0,0 +1,35 @@
# 数据库迁移(Flyway)
## 规则
- 不使用 `IF NOT EXISTS`
- 所有 DDL 在 `V{NNN}__description.sql` 中
- Quartz 表不含审计字段
- 其余业务表必须含 7 列审计字段(见 spec/01-constraints.md §审计字段)
- 迁移文件位于 `ljwx-platform-app/src/main/resources/db/migration/`
## 迁移清单
| 文件 | 说明 |
|------|------|
| V001__init_schema.sql | 创建 sys_tenant 表 |
| V002__create_user.sql | 创建 sys_user 表 |
| V003__create_role.sql | 创建 sys_role、sys_user_role 表 |
| V004__create_permission.sql | 创建 sys_permission、sys_role_permission 表 |
| V005__seed_default_tenant.sql | 插入默认租户 (id=1, name='默认租户') |
| V006__seed_admin_user.sql | 插入 admin 用户 (BCrypt hash of Admin@12345, cost=10) |
| V007__seed_permissions.sql | 插入所有权限字符串 |
| V008__seed_admin_role.sql | 插入 admin 角色并关联全部权限 |
| V009__assign_admin_role.sql | 将 admin 角色分配给 admin 用户 |
| V010__create_quartz_tables.sql | Quartz 标准 PostgreSQL DDL(无审计字段) |
| V011__create_sys_job.sql | 创建 sys_job 业务表(含审计字段) |
| V012__create_sys_dict_type.sql | 字典类型表 |
| V013__create_sys_dict_data.sql | 字典数据表 |
| V014__create_sys_config.sql | 系统配置表 |
| V015__create_sys_operation_log.sql | 操作日志表 |
| V016__create_sys_login_log.sql | 登录日志表 |
| V017__create_sys_file.sql | 文件管理表 |
| V018__create_sys_notice.sql | 通知表 |
| V019__seed_dict_data.sql | 初始字典数据(性别、状态等) |
| V020__seed_config_data.sql | 初始配置数据 |
| V021__create_indexes.sql | 常用索引 |
+84
View File
@@ -0,0 +1,84 @@
# 后端配置
> 版本号见 CLAUDE.md "版本锁定"段。
## application.yml 骨架
```yaml
server:
port: 8080
servlet:
context-path: /
spring:
application:
name: ljwx-platform
datasource:
url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:ljwx_platform}
username: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:postgres}
driver-class-name: org.postgresql.Driver
flyway:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: false
quartz:
job-store-type: jdbc
jdbc:
initialize-schema: never
properties:
org.quartz:
scheduler:
instanceName: LjwxScheduler
instanceId: AUTO
jobStore:
class: org.quartz.impl.jdbcjobstore.JobStoreTX
driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
tablePrefix: QRTZ_
isClustered: true
clusterCheckinInterval: 15000
threadPool:
threadCount: 10
servlet:
multipart:
max-file-size: 50MB
max-request-size: 50MB
cache:
type: caffeine
caffeine:
spec: maximumSize=500,expireAfterWrite=600s
mybatis:
mapper-locations: classpath*:mapper/**/*.xml
configuration:
map-underscore-to-camel-case: true
default-enum-type-handler: org.apache.ibatis.type.EnumTypeHandler
springdoc:
api-docs:
path: /v3/api-docs
swagger-ui:
path: /swagger-ui.html
ljwx:
jwt:
secret: ${JWT_SECRET:your-256-bit-secret-key-here-change-in-production}
access-token-expiration: 1800
refresh-token-expiration: 604800
file:
base-path: ${FILE_BASE_PATH:./uploads}
management:
endpoints:
web:
exposure:
include: health,info,flyway
endpoint:
health:
show-details: when-authorized
logging:
level:
com.ljwx.platform: DEBUG
org.springframework.security: DEBUG
```
+351
View File
@@ -0,0 +1,351 @@
# 前端配置
> 版本号见 CLAUDE.md "版本锁定"段。本文件中的 package.json 版本号均写 "见 CLAUDE.md",实际生成时从 CLAUDE.md 取值。
## pnpm-workspace.yaml
```yaml
packages:
- 'packages/*'
- 'ljwx-platform-admin'
- 'ljwx-platform-mobile'
- 'ljwx-platform-screen'
```
## Root package.json
```json
{
"name": "ljwx-platform-frontend",
"private": true,
"packageManager": "pnpm@10.30.1",
"engines": {
"node": ">=20.19.0 || >=22.12.0"
},
"scripts": {
"dev:admin": "pnpm --filter ljwx-platform-admin dev",
"dev:mobile": "pnpm --filter ljwx-platform-mobile dev:h5",
"dev:screen": "pnpm --filter ljwx-platform-screen dev",
"build:shared": "pnpm --filter @ljwx/shared build",
"build:admin": "pnpm run build:shared && pnpm --filter ljwx-platform-admin build",
"build:mobile": "pnpm run build:shared && pnpm --filter ljwx-platform-mobile build:h5",
"build:screen": "pnpm run build:shared && pnpm --filter ljwx-platform-screen build",
"build:all": "pnpm run build:shared && pnpm --filter './ljwx-platform-*' build",
"type-check": "pnpm --filter './ljwx-platform-*' type-check",
"lint": "pnpm --filter './ljwx-platform-*' lint"
}
}
```
## .npmrc
```
shamefully-hoist=true
strict-peer-dependencies=false
auto-install-peers=true
```
## .nvmrc
```
22.22.0
```
## .env.example
```bash
# ===== Backend =====
DB_HOST=localhost
DB_PORT=5432
DB_NAME=ljwx_platform
DB_USERNAME=postgres
DB_PASSWORD=postgres
JWT_SECRET=your-256-bit-secret-key-here-change-in-production
FILE_BASE_PATH=./uploads
# ===== Frontend =====
VITE_APP_BASE_API=/api
VITE_APP_TITLE=LJWX Platform
```
**注意:** 前端统一使用 `VITE_APP_BASE_API`。Admin / Mobile / Screen 的 `.env.development` 和 `.env.production` 均使用此变量名。
## Admin package.json
```json
{
"name": "ljwx-platform-admin",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"type-check": "vue-tsc --noEmit",
"preview": "vite preview"
},
"dependencies": {
"vue": "~3.5.28",
"vue-router": "~5.0.2",
"pinia": "~3.0.4",
"element-plus": "~2.13.2",
"@element-plus/icons-vue": "~2.3.2",
"axios": "~1.13.5",
"@vueuse/core": "~14.2.1",
"nprogress": "~0.2.0",
"dayjs": "~1.11.19",
"@ljwx/shared": "workspace:*"
},
"devDependencies": {
"vite": "~7.3.1",
"typescript": "~5.9.3",
"vue-tsc": "~3.2.4",
"@vitejs/plugin-vue": "~6.0.0",
"unplugin-auto-import": "~21.0.0",
"unplugin-vue-components": "~31.0.0",
"sass": "~1.97.3",
"@types/nprogress": "~0.2.3"
}
}
```
## Admin vite.config.ts
```typescript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import { fileURLToPath, URL } from 'node:url'
export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
resolvers: [ElementPlusResolver()],
dts: 'src/auto-imports.d.ts',
}),
Components({
resolvers: [ElementPlusResolver()],
dts: 'src/components.d.ts',
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
},
},
},
})
```
## Admin Axios 封装要点
```typescript
// src/api/request.ts
import axios from 'axios'
import type { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse } from 'axios'
import { useUserStore } from '@/stores/user'
import { ElMessage } from 'element-plus'
import router from '@/router'
const service: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 15000,
})
// 请求拦截:注入 Authorization
service.interceptors.request.use((config: InternalAxiosRequestConfig) => {
const userStore = useUserStore()
if (userStore.accessToken) {
config.headers.Authorization = `Bearer ${userStore.accessToken}`
}
return config
})
// 响应拦截:处理 401 刷新逻辑
let isRefreshing = false
let failedQueue: Array<{
resolve: (value?: unknown) => void
reject: (reason?: unknown) => void
}> = []
service.interceptors.response.use(
(response: AxiosResponse) => {
const { code, message, data } = response.data
if (code !== 200) {
ElMessage.error(message || '请求失败')
return Promise.reject(new Error(message))
}
return data
},
async (error) => {
const originalRequest = error.config
if (error.response?.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
return new Promise((resolve, reject) => {
failedQueue.push({ resolve, reject })
}).then(() => service(originalRequest))
}
originalRequest._retry = true
isRefreshing = true
try {
const userStore = useUserStore()
await userStore.refreshToken()
failedQueue.forEach(({ resolve }) => resolve(undefined))
failedQueue = []
return service(originalRequest)
} catch {
failedQueue.forEach(({ reject }) => reject(error))
failedQueue = []
const userStore = useUserStore()
userStore.logout()
router.push('/login')
return Promise.reject(error)
} finally {
isRefreshing = false
}
}
ElMessage.error(error.response?.data?.message || '网络错误')
return Promise.reject(error)
},
)
export default service
```
## Mobile package.json
```json
{
"name": "ljwx-platform-mobile",
"version": "1.0.0",
"scripts": {
"dev:h5": "uni -p h5",
"build:h5": "uni build -p h5"
},
"dependencies": {
"@dcloudio/uni-app": "~3.0.0-alpha-4060620250520001",
"@dcloudio/uni-h5": "~3.0.0-alpha-4060620250520001",
"@dcloudio/uni-components": "~3.0.0-alpha-4060620250520001",
"vue": "~3.5.28",
"pinia": "~3.0.4",
"axios": "~1.13.5",
"@ljwx/shared": "workspace:*"
},
"devDependencies": {
"@dcloudio/vite-plugin-uni": "~3.0.0-alpha-4060620250520001",
"typescript": "~5.9.3",
"vite": "~7.3.1"
}
}
```
## Mobile pages.json
```json
{
"pages": [
{ "path": "pages/login/index", "style": { "navigationBarTitleText": "登录" } },
{ "path": "pages/home/index", "style": { "navigationBarTitleText": "首页" } },
{ "path": "pages/work/index", "style": { "navigationBarTitleText": "工作台" } },
{ "path": "pages/message/index", "style": { "navigationBarTitleText": "消息" } },
{ "path": "pages/mine/index", "style": { "navigationBarTitleText": "我的" } }
],
"tabBar": {
"list": [
{ "pagePath": "pages/home/index", "text": "首页" },
{ "pagePath": "pages/work/index", "text": "工作台" },
{ "pagePath": "pages/message/index", "text": "消息" },
{ "pagePath": "pages/mine/index", "text": "我的" }
]
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "LJWX",
"navigationBarBackgroundColor": "#ffffff"
}
}
```
## Screen package.json
```json
{
"name": "ljwx-platform-screen",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"type-check": "vue-tsc --noEmit",
"preview": "vite preview"
},
"dependencies": {
"vue": "~3.5.28",
"echarts": "~6.0.0",
"@kjgl77/datav-vue3": "~1.7.4",
"axios": "~1.13.5",
"@vueuse/core": "~14.2.1",
"@ljwx/shared": "workspace:*"
},
"devDependencies": {
"vite": "~7.3.1",
"typescript": "~5.9.3",
"vue-tsc": "~3.2.4",
"@vitejs/plugin-vue": "~6.0.0",
"sass": "~1.97.3"
}
}
```
## Screen ECharts 暗色主题注册
```typescript
// src/utils/echarts-setup.ts
import * as echarts from 'echarts'
import darkTheme from './echarts-dark-theme'
echarts.registerTheme('ljwx-dark', darkTheme)
export { echarts }
```
## Screen 自适应缩放 Composable
```typescript
// src/composables/useScreenAdapt.ts
import { ref, onMounted, onUnmounted } from 'vue'
export function useScreenAdapt(designWidth = 1920, designHeight = 1080) {
const scale = ref(1)
function updateScale() {
const scaleX = window.innerWidth / designWidth
const scaleY = window.innerHeight / designHeight
scale.value = Math.min(scaleX, scaleY)
}
onMounted(() => {
updateScale()
window.addEventListener('resize', updateScale)
})
onUnmounted(() => {
window.removeEventListener('resize', updateScale)
})
return { scale }
}
```
+216
View File
@@ -0,0 +1,216 @@
# DevOps
> 版本号见 CLAUDE.md "版本锁定"段。
## 工具链安装策略
| 工具 | 推荐安装方式 | CI 跳过策略 |
|------|-------------|------------|
| JDK 21 | sdkman / actions/setup-java@v4 | 若 `java -version` 满足则跳过 |
| Maven | Maven Wrapper `./mvnw` | 始终使用 wrapper |
| Node.js | fnm / actions/setup-node@v4 | 若 `node -v` 满足则跳过 |
| pnpm | `corepack enable && corepack prepare` | corepack 自动处理 |
| Docker | 系统级 / docker/setup-buildx-action | Testcontainers 必需 |
## Docker Compose
```yaml
version: '3.8'
services:
postgres:
image: postgres:16.12-alpine
container_name: ljwx-postgres
ports:
- "${DB_PORT:-5432}:5432"
environment:
POSTGRES_DB: ${DB_NAME:-ljwx_platform}
POSTGRES_USER: ${DB_USERNAME:-postgres}
POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME:-postgres} -d ${DB_NAME:-ljwx_platform}"]
interval: 10s
timeout: 5s
retries: 5
volumes:
pgdata:
```
## CI Gate 脚本
### scripts/gates/gate-all.sh
```bash
#!/usr/bin/env bash
set -euo pipefail
echo "===== LJWX Platform CI Gate ====="
echo "[1/6] Manifest Check..."
bash scripts/gates/gate-manifest.sh
echo "[2/6] Backend Compile + Unit Test..."
bash scripts/gates/gate-compile.sh
echo "[3/6] Integration Test (Testcontainers)..."
bash scripts/gates/gate-integration.sh
echo "[4/6] Contract Check (OpenAPI)..."
bash scripts/gates/gate-contract.sh
echo "[5/6] NFR Check..."
bash scripts/gates/gate-nfr.sh
echo "[6/6] Frontend Build..."
pnpm install --frozen-lockfile
pnpm run build:all
echo "===== ALL GATES PASSED ====="
```
### scripts/gates/gate-compile.sh
```bash
#!/usr/bin/env bash
set -euo pipefail
./mvnw clean verify -DskipITs -T1C
```
### scripts/gates/gate-integration.sh
```bash
#!/usr/bin/env bash
set -euo pipefail
./mvnw verify -PIT -Dfailsafe.rerunFailingTestsCount=1
```
### scripts/gates/gate-manifest.sh
```bash
#!/usr/bin/env bash
set -euo pipefail
PHASE_FILE="${1:-PHASE_MANIFEST.txt}"
echo "===== Phase-Local Manifest Check ====="
# ---- Phase-local file existence check ----
if [[ -f "$PHASE_FILE" ]]; then
MISSING=0
while IFS= read -r file; do
[[ -z "$file" || "$file" =~ ^# ]] && continue
if [[ ! -e "$file" ]]; then
echo "MISSING: $file"
MISSING=$((MISSING + 1))
fi
done < "$PHASE_FILE"
if [[ $MISSING -gt 0 ]]; then
echo "FAIL: $MISSING file(s) missing from phase manifest."
exit 1
fi
echo "Phase-local file check: OK"
else
echo "WARN: No phase manifest file found ($PHASE_FILE). Skipping phase-local check."
fi
# ---- Check for ^ in package.json (repo-wide) ----
CARET_FOUND=$(grep -RIn '"\^' \
package.json \
packages/ \
ljwx-platform-admin/ \
ljwx-platform-mobile/ \
ljwx-platform-screen/ \
2>/dev/null || true)
if [[ -n "$CARET_FOUND" ]]; then
echo "FAIL: Found caret (^) version specifiers. Use tilde (~) only."
echo "$CARET_FOUND"
exit 1
fi
echo "Caret check: OK"
# ---- Check env var consistency (all three frontends) ----
DEPRECATED_VAR=$(grep -RIn 'VITE_API_BASE_URL' \
ljwx-platform-admin/ \
ljwx-platform-mobile/ \
ljwx-platform-screen/ \
2>/dev/null || true)
if [[ -n "$DEPRECATED_VAR" ]]; then
echo "FAIL: Found deprecated VITE_API_BASE_URL. Use VITE_APP_BASE_API."
echo "$DEPRECATED_VAR"
exit 1
fi
echo "Env var consistency check: OK"
# ---- Check audit fields in SQL migrations (7 columns) ----
AUDIT_COLUMNS="tenant_id created_by created_time updated_by updated_time deleted version"
for sql in ljwx-platform-app/src/main/resources/db/migration/V*.sql; do
[[ ! -f "$sql" ]] && continue
if grep -qi "QRTZ_" "$sql"; then continue; fi
if grep -qi "CREATE TABLE" "$sql"; then
for col in $AUDIT_COLUMNS; do
if ! grep -qi "$col" "$sql"; then
echo "FAIL: $sql has CREATE TABLE but missing audit column: $col"
exit 1
fi
done
fi
done
echo "Audit field check: OK"
echo "===== Manifest Check PASSED ====="
```
### scripts/gates/gate-contract.sh
```bash
#!/usr/bin/env bash
set -euo pipefail
bash scripts/tools/export-openapi.sh
if [[ ! -f docs/contracts/openapi.json ]]; then
echo "FAIL: openapi.json not generated"
exit 1
fi
echo "Contract check PASSED"
```
### scripts/acceptance/smoke-test.sh
```bash
#!/usr/bin/env bash
set -euo pipefail
BASE_URL="${1:-http://localhost:8080}"
echo "===== Smoke Test ====="
# Health check
for i in $(seq 1 30); do
if curl -sf "$BASE_URL/actuator/health" > /dev/null 2>&1; then
echo "Health check passed (attempt $i)"
break
fi
if [[ $i -eq 30 ]]; then
echo "FAIL: Health check timed out"
exit 1
fi
sleep 2
done
# Login test
RESP=$(curl -sf -X POST "$BASE_URL/api/auth/login" \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"Admin@12345"}')
CODE=$(echo "$RESP" | grep -o '"code":[0-9]*' | head -1 | cut -d: -f2)
if [[ "$CODE" != "200" ]]; then
echo "FAIL: Login returned code $CODE"
echo "$RESP"
exit 1
fi
echo "===== Smoke Test PASSED ====="
```
+45
View File
@@ -0,0 +1,45 @@
# 输出规则
## Phase 输出格式
每个 Phase 的输出必须包含以下段落(按此顺序):
```
PHASE {N}: {标题}
PHASE-LOCAL MANIFEST
(仅列出本阶段新增或修改的文件路径)
NEW FILES
(完整文件内容,不省略)
PATCHES
(对已有文件的 unified diff)
TESTS
(本阶段应通过的测试)
COMMANDS
(本阶段执行的验证命令)
ACCEPTANCE
(本阶段的验收条件)
RISKS & ROLLBACK
(风险点与回退方案)
```
## 强制规则
1. **不省略**: 每个 NEW FILES 段必须输出完整文件内容。禁止 `// ... 省略 ...` 或 `/* same as before */`
2. **Unified Diff**: 对已有文件的修改使用标准 `--- a/path` / `+++ b/path` 格式
3. **Phase-Local Manifest**: 每个 Phase 的 MANIFEST 仅列出本阶段的文件,不列未来 Phase 的文件
4. **PATCHES 最小化**: 仅修改与本 Phase 直接相关的文件。禁止顺手重构、重新格式化、批量重写无关文件
## Phase 19 Final Manifest
Phase 19 的 gate-manifest.sh 执行 FULL 模式:遍历仓库所有预期文件路径,确认每个文件存在且非空。此时生成 FULL_MANIFEST.txt,包含仓库内所有文件的完整列表。
## Bundle 执行规则
允许一次指令执行多个 Phase(Bundle),但每个 Phase 必须有独立的输出块。例如执行 Phase 2+3 时,输出中必须分别有 `## PHASE 2` 和 `## PHASE 3` 两个完整块,各含自己的 MANIFEST、NEW FILES、ACCEPTANCE 等。
+23
View File
@@ -0,0 +1,23 @@
# LJWX Platform 规格文档索引
## 文档职责边界
| 文件 | 内容 | 单一事实源规则 |
|------|------|---------------|
| `CLAUDE.md` | 硬规则 + 版本锁定 + 代码风格 + 反模式 | **版本号的唯一来源**。其他文件引用不重复 |
| `spec/01-constraints.md` | 审计字段、多租户、JWT、RBAC、Quartz、日志、文件、缓存、TS 约束的完整定义 | 约束细节的唯一来源 |
| `spec/02-architecture.md` | 仓库结构、模块依赖 DAG、Core 接口定义、POM 依赖声明 | 架构的唯一来源 |
| `spec/03-api.md` | 统一响应格式、错误码、完整路由表 | API 合约的唯一来源 |
| `spec/04-database.md` | Flyway 迁移文件清单 + DDL 规则 | 数据库 schema 的唯一来源 |
| `spec/05-backend-config.md` | application.yml 骨架 | 后端配置的唯一来源 |
| `spec/06-frontend-config.md` | pnpm-workspace、root package.json、.npmrc、.nvmrc、.env.example、Admin / Mobile / Screen 各端配置 | 前端配置的唯一来源 |
| `spec/07-devops.md` | Docker Compose、CI Gate 脚本、smoke-test、工具链安装策略 | DevOps 的唯一来源 |
| `spec/08-output-rules.md` | Phase 输出格式、完整性规则 | 输出规范的唯一来源 |
| `spec/phase/phase-{NN}.md` | 每个 Phase 的具体任务、读取清单、验收条件 | Phase 执行计划的唯一来源 |
## 防漂移规则
1. **版本号**只在 `CLAUDE.md` 的"版本锁定"段中维护。其他 spec 文件需要引用版本时,写"版本见 CLAUDE.md"
2. **同一约束**不在两个文件中重复定义。在唯一来源中定义,其他文件通过"见 spec/XX-xxx.md §章节名"引用
3. 如发现任何不一致,以"单一事实源"文件为准
4. 变更日志和审阅记录存放在 `docs/adr/` 目录,不进入生成上下文
+82
View File
@@ -0,0 +1,82 @@
---
phase: 0
title: "Project Skeleton"
targets:
backend: true
frontend: true
depends_on: []
bundle_with: [1]
scope:
- "pom.xml"
- ".mvn/**"
- "pnpm-workspace.yaml"
- "package.json"
- ".npmrc"
- ".nvmrc"
- "docker-compose.yml"
- ".env.example"
- ".gitignore"
- ".editorconfig"
- "scripts/gates/**"
- "scripts/tools/**"
- "scripts/acceptance/**"
- "PHASE_MANIFEST.txt"
---
# Phase 0: Skeleton
## 读取清单
- `CLAUDE.md`(自动加载)
- `spec/02-architecture.md` — §仓库结构
- `spec/06-frontend-config.md` — §pnpm-workspace.yaml、§Root package.json、§.npmrc、§.nvmrc、§.env.example
- `spec/07-devops.md` — §Docker Compose、§CI Gate 脚本全部
- `spec/08-output-rules.md` — 全文
## 任务
创建项目骨架:所有配置文件和 CI 脚本。不包含任何 Java 源码或前端源码。
## Phase-Local Manifest
```
pom.xml
.mvn/wrapper/maven-wrapper.properties
pnpm-workspace.yaml
package.json
.npmrc
.nvmrc
docker-compose.yml
.env.example
.gitignore
.editorconfig
scripts/gates/gate-all.sh
scripts/gates/gate-compile.sh
scripts/gates/gate-integration.sh
scripts/gates/gate-contract.sh
scripts/gates/gate-manifest.sh
scripts/gates/gate-nfr.sh
scripts/tools/export-openapi.sh
scripts/acceptance/smoke-test.sh
PHASE_MANIFEST.txt
```
## 验收条件
1. `pom.xml` 中 Java source/target = 21,Spring Boot parent 版本来自 CLAUDE.md
2. `package.json` 的 `packageManager` 字段与 CLAUDE.md 一致
3. `.nvmrc` 内容与 CLAUDE.md 一致
4. `docker-compose.yml` 使用 `postgres:16.12-alpine`
5. 所有 `package.json` 中无 `^`
6. `.env.example` 使用 `VITE_APP_BASE_API`,无 `VITE_API_BASE_URL`
7. `gate-manifest.sh` 包含 7 列审计字段循环检查 + caret 全局扫描 + env 变量三端扫描
## 验证命令
```bash
grep -RIn '"\^' package.json || echo "OK: No caret in root"
grep -RIn 'VITE_API_BASE_URL' .env.example && echo "FAIL" || echo "OK"
```
## 可 Bundle
可与 Phase 1 一起执行,但必须分开两个 Phase 块输出。
+50
View File
@@ -0,0 +1,50 @@
---
phase: 1
title: "Core Module"
targets:
backend: true
frontend: false
depends_on: [0]
bundle_with: [0]
scope:
- "ljwx-platform-core/**"
---
# Phase 1: Core Module
## 读取清单
- `CLAUDE.md`(自动加载)
- `spec/01-constraints.md` — §审计字段、§多租户行级隔离
- `spec/02-architecture.md` — §模块依赖图、§Core 模块关键接口
- `spec/03-api.md` — §统一响应、§错误码
- `spec/08-output-rules.md`
## 任务
实现 ljwx-platform-core 模块:Result、ErrorCode、BaseEntity、SnowflakeIdGenerator、CurrentUserHolder 接口、CurrentTenantHolder 接口。
## Phase-Local Manifest
```
ljwx-platform-core/pom.xml
ljwx-platform-core/src/main/java/com/ljwx/platform/core/result/Result.java
ljwx-platform-core/src/main/java/com/ljwx/platform/core/result/ErrorCode.java
ljwx-platform-core/src/main/java/com/ljwx/platform/core/result/PageResult.java
ljwx-platform-core/src/main/java/com/ljwx/platform/core/id/SnowflakeIdGenerator.java
ljwx-platform-core/src/main/java/com/ljwx/platform/core/entity/BaseEntity.java
ljwx-platform-core/src/main/java/com/ljwx/platform/core/context/CurrentUserHolder.java
ljwx-platform-core/src/main/java/com/ljwx/platform/core/context/CurrentTenantHolder.java
```
## 验收条件
1. `pom.xml` 无对其他 ljwx 模块的依赖
2. Result 类包含 code、message、data、traceId 字段
3. ErrorCode 枚举包含 spec/03-api.md 中定义的所有错误码
4. BaseEntity 包含 7 个审计字段(匹配 spec/01-constraints.md)
5. CurrentUserHolder 和 CurrentTenantHolder 是接口(非实现类)
6. `./mvnw compile -pl ljwx-platform-core` 通过
## 可 Bundle
可与 Phase 0 一起执行。
+51
View File
@@ -0,0 +1,51 @@
---
phase: 2
title: "Data Module"
targets:
backend: true
frontend: false
depends_on: [1]
bundle_with: [3]
scope:
- "ljwx-platform-data/**"
---
# Phase 2: Data Module
## 读取清单
- `CLAUDE.md`(自动加载)
- `spec/01-constraints.md` — §审计字段、§多租户行级隔离
- `spec/02-architecture.md` — §模块依赖图、§Core 模块关键接口、§POM 依赖声明(data 部分)
- `spec/08-output-rules.md`
## 任务
实现 ljwx-platform-data 模块:AuditFieldInterceptor、TenantLineInterceptor、MyBatis 配置。
## 关键约束
- data 模块**仅依赖 core**,import 路径只能出现 `com.ljwx.platform.core.*`
- AuditFieldInterceptor 通过 `CurrentUserHolder`(core 接口)获取用户 ID
- TenantLineInterceptor 通过 `CurrentTenantHolder`(core 接口)获取租户 ID
- 实现类由 security 模块在运行时通过 Spring DI 提供
## Phase-Local Manifest
```
ljwx-platform-data/pom.xml
ljwx-platform-data/src/main/java/com/ljwx/platform/data/interceptor/AuditFieldInterceptor.java
ljwx-platform-data/src/main/java/com/ljwx/platform/data/interceptor/TenantLineInterceptor.java
ljwx-platform-data/src/main/java/com/ljwx/platform/data/config/MyBatisConfig.java
```
## 验收条件
1. `pom.xml` 依赖仅含 `ljwx-platform-core`,无 `ljwx-platform-security`
2. 全部 import 语句无 `com.ljwx.platform.security`
3. AuditFieldInterceptor 处理 INSERT 和 UPDATE
4. TenantLineInterceptor 自动追加 `WHERE tenant_id = ?`
5. `./mvnw compile -pl ljwx-platform-data` 通过
## 可 Bundle
可与 Phase 3 一起执行,但分开输出。
+57
View File
@@ -0,0 +1,57 @@
---
phase: 3
title: "Security Module"
targets:
backend: true
frontend: false
depends_on: [1]
bundle_with: [2]
scope:
- "ljwx-platform-security/**"
---
# Phase 3: Security Module
## 读取清单
- `CLAUDE.md`(自动加载)
- `spec/01-constraints.md` — §JWT 认证
- `spec/02-architecture.md` — §模块依赖图、§Core 模块关键接口、§POM 依赖声明(security 部分)
- `spec/08-output-rules.md`
## 任务
实现 ljwx-platform-security 模块:JWT 工具类、SecurityContextUserHolder / SecurityContextTenantHolder 实现、Security Filter Config。
## 关键约束
- security 模块**仅依赖 core**,禁止依赖 data
- SecurityContextUserHolder 实现 `CurrentUserHolder` 接口(core 中定义)
- SecurityContextTenantHolder 实现 `CurrentTenantHolder` 接口(core 中定义)
- JWT authorities claim name = `authorities`,不添加 ROLE_ 前缀
- JWT converter:读取 authorities → SimpleGrantedAuthority
## Phase-Local Manifest
```
ljwx-platform-security/pom.xml
ljwx-platform-security/src/main/java/com/ljwx/platform/security/context/SecurityContextUserHolder.java
ljwx-platform-security/src/main/java/com/ljwx/platform/security/context/SecurityContextTenantHolder.java
ljwx-platform-security/src/main/java/com/ljwx/platform/security/jwt/JwtTokenProvider.java
ljwx-platform-security/src/main/java/com/ljwx/platform/security/jwt/JwtProperties.java
ljwx-platform-security/src/main/java/com/ljwx/platform/security/filter/JwtAuthenticationFilter.java
ljwx-platform-security/src/main/java/com/ljwx/platform/security/config/SecurityConfig.java
```
## 验收条件
1. `pom.xml` 依赖仅含 `ljwx-platform-core`,无 `ljwx-platform-data`
2. 全部 import 无 `com.ljwx.platform.data`
3. SecurityContextUserHolder 实现 CurrentUserHolder
4. SecurityContextTenantHolder 实现 CurrentTenantHolder
5. JwtTokenProvider 使用 HS256,支持 access/refresh 两种 type
6. SecurityConfig 中 `/api/auth/login` 和 `/api/auth/refresh` 允许匿名
7. `./mvnw compile -pl ljwx-platform-security` 通过
## 可 Bundle
可与 Phase 2 一起执行。
+43
View File
@@ -0,0 +1,43 @@
---
phase: 4
title: "Web Module"
targets:
backend: true
frontend: false
depends_on: [2, 3]
bundle_with: [5]
scope:
- "ljwx-platform-web/**"
---
# Phase 4: Web Module
## 读取清单
- `CLAUDE.md`(自动加载)
- `spec/02-architecture.md` — §模块依赖图、§POM 依赖声明(web 部分)
- `spec/03-api.md` — §统一响应、§错误码
- `spec/08-output-rules.md`
## 任务
实现 ljwx-platform-web 模块:GlobalExceptionHandler、ResponseAdvice、WebMvcConfig。
## Phase-Local Manifest
```
ljwx-platform-web/pom.xml
ljwx-platform-web/src/main/java/com/ljwx/platform/web/advice/GlobalExceptionHandler.java
ljwx-platform-web/src/main/java/com/ljwx/platform/web/advice/ResponseAdvice.java
ljwx-platform-web/src/main/java/com/ljwx/platform/web/config/WebMvcConfig.java
```
## 验收条件
1. `pom.xml` 依赖含 `ljwx-platform-security`(core 经传递获得)
2. GlobalExceptionHandler 处理常见异常并返回 Result 格式
3. ErrorCode 中定义的所有错误码均有对应异常处理
4. `./mvnw compile -pl ljwx-platform-web` 通过
## 可 Bundle
可与 Phase 5 一起执行。
+67
View File
@@ -0,0 +1,67 @@
---
phase: 5
title: "App Skeleton"
targets:
backend: true
frontend: false
depends_on: [4]
bundle_with: [4]
scope:
- "ljwx-platform-app/pom.xml"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/LjwxPlatformApplication.java"
- "ljwx-platform-app/src/main/resources/application.yml"
- "ljwx-platform-app/src/main/resources/db/migration/V001__init_schema.sql"
- "ljwx-platform-app/src/main/resources/db/migration/V002__create_user.sql"
- "ljwx-platform-app/src/main/resources/db/migration/V003__create_role.sql"
- "ljwx-platform-app/src/main/resources/db/migration/V004__create_permission.sql"
- "ljwx-platform-app/src/main/resources/db/migration/V005__seed_default_tenant.sql"
- "ljwx-platform-app/src/main/resources/db/migration/V006__seed_admin_user.sql"
- "ljwx-platform-app/src/main/resources/db/migration/V007__seed_permissions.sql"
- "ljwx-platform-app/src/main/resources/db/migration/V008__seed_admin_role.sql"
- "ljwx-platform-app/src/main/resources/db/migration/V009__assign_admin_role.sql"
---
# Phase 5: App Skeleton
## 读取清单
- `CLAUDE.md`(自动加载)
- `spec/02-architecture.md` — §POM 依赖声明(app 部分)
- `spec/04-database.md` — V001 ~ V009
- `spec/05-backend-config.md` — §application.yml 骨架
- `spec/01-constraints.md` — §审计字段、§RBAC 权限
- `spec/08-output-rules.md`
## 任务
实现 ljwx-platform-app 骨架:Spring Boot Main、application.yml、Flyway V001-V009(基础表 + 种子数据)。
## Phase-Local Manifest
```
ljwx-platform-app/pom.xml
ljwx-platform-app/src/main/java/com/ljwx/platform/app/LjwxPlatformApplication.java
ljwx-platform-app/src/main/resources/application.yml
ljwx-platform-app/src/main/resources/db/migration/V001__init_schema.sql
ljwx-platform-app/src/main/resources/db/migration/V002__create_user.sql
ljwx-platform-app/src/main/resources/db/migration/V003__create_role.sql
ljwx-platform-app/src/main/resources/db/migration/V004__create_permission.sql
ljwx-platform-app/src/main/resources/db/migration/V005__seed_default_tenant.sql
ljwx-platform-app/src/main/resources/db/migration/V006__seed_admin_user.sql
ljwx-platform-app/src/main/resources/db/migration/V007__seed_permissions.sql
ljwx-platform-app/src/main/resources/db/migration/V008__seed_admin_role.sql
ljwx-platform-app/src/main/resources/db/migration/V009__assign_admin_role.sql
```
## 验收条件
1. `pom.xml` 依赖含 `ljwx-platform-web` + `ljwx-platform-data`
2. application.yml 与 spec/05-backend-config.md 一致
3. V001-V004 的 CREATE TABLE 均含 7 列审计字段
4. V006 中 admin 密码使用 BCrypt cost=10 的 hash
5. V007 包含 spec/01-constraints.md §RBAC 中的所有权限字符串
6. 无 `IF NOT EXISTS`
7. `./mvnw compile -pl ljwx-platform-app` 通过
## 可 Bundle
可与 Phase 4 一起执行。
+43
View File
@@ -0,0 +1,43 @@
---
phase: 6
title: "AI Context Docs"
targets:
backend: false
frontend: false
depends_on: [5]
bundle_with: []
scope:
- "CLAUDE.md"
- "spec.md"
- "spec/INDEX.md"
---
# Phase 6: AI Context Docs
## 读取清单
- `CLAUDE.md`(自动加载)
- `spec/08-output-rules.md`
## 任务
确认并微调 CLAUDE.md、spec.md(stub)、spec/INDEX.md。此时这些文件应已存在(在开始使用 Claude Code 前手动创建)。本 Phase 主要确认内容正确、无遗漏。
如果 AGENTS.md 需要创建(用于非 Claude Code 的 AI 工具),在本 Phase 生成。
## Phase-Local Manifest
```
CLAUDE.md
spec.md
spec/INDEX.md
```
## 验收条件
1. CLAUDE.md 中 Current Phase 已更新到 6
2. spec.md 是 stub,指向 CLAUDE.md 和 spec/INDEX.md
3. spec/INDEX.md 包含完整的文档索引表和防漂移规则
## 说明
本 Phase 产出量小,可与相邻 Phase Bundle。
+61
View File
@@ -0,0 +1,61 @@
---
phase: 7
title: "Quartz Integration"
targets:
backend: true
frontend: false
depends_on: [5]
bundle_with: [8]
scope:
- "ljwx-platform-app/src/main/resources/db/migration/V010__create_quartz_tables.sql"
- "ljwx-platform-app/src/main/resources/db/migration/V011__create_sys_job.sql"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/JobController.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/appservice/JobAppService.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/domain/entity/SysJob.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/infra/mapper/SysJobMapper.java"
- "ljwx-platform-app/src/main/resources/mapper/SysJobMapper.xml"
---
# Phase 7: Quartz Integration
## 读取清单
- `CLAUDE.md`(自动加载)
- `spec/01-constraints.md` — §Quartz 调度
- `spec/03-api.md` — §Jobs 路由
- `spec/04-database.md` — V010、V011
- `spec/08-output-rules.md`
## 任务
Quartz 集成:V010(Quartz 标准表)、V011(sys_job 业务表)、Job Controller / Service / Mapper、per-tenant 调度。
## 关键约束
- V010:Quartz 标准 PostgreSQL DDL,QRTZ_ 前缀,**无审计字段**,**无 IF NOT EXISTS**
- V011:sys_job 业务表,**含 7 列审计字段**
- JobKey 格式:`(name="{jobId}", group="TENANT_{tenantId}")`
- Controller 每个方法必须 `@PreAuthorize`
## Phase-Local Manifest
```
ljwx-platform-app/src/main/resources/db/migration/V010__create_quartz_tables.sql
ljwx-platform-app/src/main/resources/db/migration/V011__create_sys_job.sql
ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/JobController.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/appservice/JobAppService.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/domain/entity/SysJob.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/infra/mapper/SysJobMapper.java
ljwx-platform-app/src/main/resources/mapper/SysJobMapper.xml
```
## 验收条件
1. V010 含 QRTZ_ 表,无审计字段,无 IF NOT EXISTS
2. V011 含 7 列审计字段
3. JobController 所有方法有 @PreAuthorize,权限字符串匹配 spec/03-api.md
4. per-tenant 隔离:JobKey group 含 `TENANT_`
5. `./mvnw compile -pl ljwx-platform-app` 通过
## 可 Bundle
可与 Phase 8 一起执行。
+81
View File
@@ -0,0 +1,81 @@
---
phase: 8
title: "Dict and Config"
targets:
backend: true
frontend: false
depends_on: [7]
bundle_with: [7]
scope:
- "ljwx-platform-app/src/main/resources/db/migration/V012__create_sys_dict_type.sql"
- "ljwx-platform-app/src/main/resources/db/migration/V013__create_sys_dict_data.sql"
- "ljwx-platform-app/src/main/resources/db/migration/V014__create_sys_config.sql"
- "ljwx-platform-app/src/main/resources/db/migration/V019__seed_dict_data.sql"
- "ljwx-platform-app/src/main/resources/db/migration/V020__seed_config_data.sql"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/DictController.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/ConfigController.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/appservice/DictAppService.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/appservice/ConfigAppService.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/domain/entity/SysDictType.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/domain/entity/SysDictData.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/domain/entity/SysConfig.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/infra/mapper/SysDictTypeMapper.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/infra/mapper/SysDictDataMapper.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/infra/mapper/SysConfigMapper.java"
- "ljwx-platform-app/src/main/resources/mapper/SysDictTypeMapper.xml"
- "ljwx-platform-app/src/main/resources/mapper/SysDictDataMapper.xml"
- "ljwx-platform-app/src/main/resources/mapper/SysConfigMapper.xml"
---
# Phase 8: Dict & Config
## 读取清单
- `CLAUDE.md`(自动加载)
- `spec/01-constraints.md` — §缓存策略、§审计字段
- `spec/03-api.md` — §Dicts 路由、§Configs 路由
- `spec/04-database.md` — V012 ~ V014、V019 ~ V020
- `spec/08-output-rules.md`
## 任务
字典和系统配置 CRUD + Caffeine 缓存:V012-V014(表结构)、V019-V020(种子数据)、Controller / Service / Mapper。
## 关键约束
- Caffeine 缓存,TTL = 10 min,由 Spring Boot 管理
- 不使用 Redis / MQ / JPA
- 所有 CREATE TABLE 含 7 列审计字段
## Phase-Local Manifest
```
ljwx-platform-app/src/main/resources/db/migration/V012__create_sys_dict_type.sql
ljwx-platform-app/src/main/resources/db/migration/V013__create_sys_dict_data.sql
ljwx-platform-app/src/main/resources/db/migration/V014__create_sys_config.sql
ljwx-platform-app/src/main/resources/db/migration/V019__seed_dict_data.sql
ljwx-platform-app/src/main/resources/db/migration/V020__seed_config_data.sql
ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/DictController.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/ConfigController.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/appservice/DictAppService.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/appservice/ConfigAppService.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/domain/entity/SysDictType.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/domain/entity/SysDictData.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/domain/entity/SysConfig.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/infra/mapper/SysDictTypeMapper.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/infra/mapper/SysDictDataMapper.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/infra/mapper/SysConfigMapper.java
ljwx-platform-app/src/main/resources/mapper/SysDictTypeMapper.xml
ljwx-platform-app/src/main/resources/mapper/SysDictDataMapper.xml
ljwx-platform-app/src/main/resources/mapper/SysConfigMapper.xml
```
## 验收条件
1. V012-V014 含 7 列审计字段,无 IF NOT EXISTS
2. DictAppService / ConfigAppService 使用 `@Cacheable` / `@CacheEvict`
3. Controller 方法均有 @PreAuthorize
4. 编译通过
## 可 Bundle
可与 Phase 7 一起执行。
+94
View File
@@ -0,0 +1,94 @@
---
phase: 9
title: "Logs Notice and File"
targets:
backend: true
frontend: false
depends_on: [8]
bundle_with: [10]
scope:
- "ljwx-platform-app/src/main/resources/db/migration/V015__create_sys_operation_log.sql"
- "ljwx-platform-app/src/main/resources/db/migration/V016__create_sys_login_log.sql"
- "ljwx-platform-app/src/main/resources/db/migration/V017__create_sys_file.sql"
- "ljwx-platform-app/src/main/resources/db/migration/V018__create_sys_notice.sql"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/OperationLogController.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/LoginLogController.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/FileController.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/NoticeController.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/appservice/OperationLogAppService.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/appservice/FileAppService.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/appservice/NoticeAppService.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/domain/entity/SysOperationLog.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/domain/entity/SysLoginLog.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/domain/entity/SysFile.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/domain/entity/SysNotice.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/infra/mapper/SysOperationLogMapper.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/infra/mapper/SysLoginLogMapper.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/infra/mapper/SysFileMapper.java"
- "ljwx-platform-app/src/main/java/com/ljwx/platform/app/infra/mapper/SysNoticeMapper.java"
- "ljwx-platform-app/src/main/resources/mapper/SysOperationLogMapper.xml"
- "ljwx-platform-app/src/main/resources/mapper/SysLoginLogMapper.xml"
- "ljwx-platform-app/src/main/resources/mapper/SysFileMapper.xml"
- "ljwx-platform-app/src/main/resources/mapper/SysNoticeMapper.xml"
---
# Phase 9: Logs, Notice & File
## 读取清单
- `CLAUDE.md`(自动加载)
- `spec/01-constraints.md` — §操作日志、§文件管理、§审计字段
- `spec/03-api.md` — §Logs、§Files、§Notices 路由
- `spec/04-database.md` — V015 ~ V018
- `spec/08-output-rules.md`
## 任务
操作日志(异步 + 脱敏)、登录日志、通知、文件管理:V015-V018 + 全部 Controller / Service / Mapper。
## 关键约束
- 操作日志异步线程池:core=2, max=4, queue=1024
- 日志体 > 4096 字节截断
- 脱敏:password → `***`,phone → 中间四位 `*`,idCard → 中间段 `*`
- 文件:Snowflake ID 命名,50MB 限制,白名单后缀,路径含 tenant_id
## Phase-Local Manifest
```
ljwx-platform-app/src/main/resources/db/migration/V015__create_sys_operation_log.sql
ljwx-platform-app/src/main/resources/db/migration/V016__create_sys_login_log.sql
ljwx-platform-app/src/main/resources/db/migration/V017__create_sys_file.sql
ljwx-platform-app/src/main/resources/db/migration/V018__create_sys_notice.sql
ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/OperationLogController.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/LoginLogController.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/FileController.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/controller/NoticeController.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/appservice/OperationLogAppService.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/appservice/FileAppService.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/appservice/NoticeAppService.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/domain/entity/SysOperationLog.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/domain/entity/SysLoginLog.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/domain/entity/SysFile.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/domain/entity/SysNotice.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/infra/mapper/SysOperationLogMapper.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/infra/mapper/SysLoginLogMapper.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/infra/mapper/SysFileMapper.java
ljwx-platform-app/src/main/java/com/ljwx/platform/app/infra/mapper/SysNoticeMapper.java
ljwx-platform-app/src/main/resources/mapper/SysOperationLogMapper.xml
ljwx-platform-app/src/main/resources/mapper/SysLoginLogMapper.xml
ljwx-platform-app/src/main/resources/mapper/SysFileMapper.xml
ljwx-platform-app/src/main/resources/mapper/SysNoticeMapper.xml
```
## 验收条件
1. V015-V018 含 7 列审计字段(日志表也需要 tenant_id 和 created_time 等)
2. 操作日志使用异步线程池
3. 脱敏逻辑已实现
4. 文件上传有后缀白名单校验和 50MB 限制
5. Controller 方法均有 @PreAuthorize
6. 编译通过
## 可 Bundle
可与 Phase 10 一起执行。
+44
View File
@@ -0,0 +1,44 @@
---
phase: 10
title: "Index and Contract"
targets:
backend: true
frontend: false
depends_on: [9]
bundle_with: [9]
scope:
- "ljwx-platform-app/src/main/resources/db/migration/V021__create_indexes.sql"
- "scripts/tools/export-openapi.sh"
- "docs/contracts/.gitkeep"
---
# Phase 10: Index & Contract
## 读取清单
- `CLAUDE.md`(自动加载)
- `spec/04-database.md` — V021
- `spec/05-backend-config.md` — §springdoc 部分
- `spec/08-output-rules.md`
## 任务
V021(常用索引)、OpenAPI export 脚本、springdoc 配置验证。
## Phase-Local Manifest
```
ljwx-platform-app/src/main/resources/db/migration/V021__create_indexes.sql
scripts/tools/export-openapi.sh
docs/contracts/.gitkeep
```
## 验收条件
1. V021 为常用字段创建索引(tenant_id、username、created_time 等)
2. export-openapi.sh 可启动应用并导出 openapi.json
3. springdoc 在 application.yml 中配置正确
4. 编译通过
## 可 Bundle
可与 Phase 9 一起执行。
+50
View File
@@ -0,0 +1,50 @@
---
phase: 11
title: "Shared Package"
targets:
backend: false
frontend: true
depends_on: [10]
bundle_with: []
scope:
- "packages/shared/**"
---
# Phase 11: Shared Package
## 读取清单
- `CLAUDE.md`(自动加载)
- `spec/03-api.md` — §统一响应、§错误码(用于类型定义)
- `spec/06-frontend-config.md` — §Root package.json(检查 workspace 配置)
- `spec/01-constraints.md` — §TypeScript 约束、§RBAC 权限(用于常量)
- `spec/08-output-rules.md`
## 任务
前端共享 TS 包 packages/shared:types(API 类型定义)、constants(错误码、权限字符串)、utils、tsup 构建。
## Phase-Local Manifest
```
packages/shared/package.json
packages/shared/tsconfig.json
packages/shared/tsup.config.ts
packages/shared/src/index.ts
packages/shared/src/types/index.ts
packages/shared/src/types/api.ts
packages/shared/src/types/user.ts
packages/shared/src/types/role.ts
packages/shared/src/types/common.ts
packages/shared/src/constants/index.ts
packages/shared/src/constants/error-codes.ts
packages/shared/src/constants/permissions.ts
packages/shared/src/utils/index.ts
```
## 验收条件
1. 无 `any` 类型
2. tsconfig `strict: true`
3. 错误码与 spec/03-api.md 一致
4. 权限常量与 spec/01-constraints.md §RBAC 一致
5. `pnpm run build:shared` 通过
+67
View File
@@ -0,0 +1,67 @@
---
phase: 12
title: "Admin Scaffold"
targets:
backend: false
frontend: true
depends_on: [11]
bundle_with: []
scope:
- "ljwx-platform-admin/**"
---
# Phase 12: Admin Scaffold
## 读取清单
- `CLAUDE.md`(自动加载)
- `spec/06-frontend-config.md` — §Admin package.json、§Admin vite.config.ts、§Admin Axios 封装
- `spec/01-constraints.md` — §JWT 认证(Refresh 流程)、§Vue Router 5 兼容性约束、§TypeScript 约束
- `spec/08-output-rules.md`
## 任务
Vue 3 + Vite + Element Plus 骨架:router(vue-router @5 API)、stores(user store 含 token 管理)、layouts(侧边栏 + 顶栏)、login 页面。
## 关键约束
- **必须使用 vue-router @5 API**,禁止按 v4 写
- 无 `any`,`strict: true`
- Axios baseURL 使用 `import.meta.env.VITE_APP_BASE_API`
- 401 刷新逻辑参考 spec/06-frontend-config.md §Axios 封装
## Phase-Local Manifest
```
ljwx-platform-admin/package.json
ljwx-platform-admin/vite.config.ts
ljwx-platform-admin/tsconfig.json
ljwx-platform-admin/tsconfig.app.json
ljwx-platform-admin/tsconfig.node.json
ljwx-platform-admin/env.d.ts
ljwx-platform-admin/.env.development
ljwx-platform-admin/.env.production
ljwx-platform-admin/index.html
ljwx-platform-admin/src/main.ts
ljwx-platform-admin/src/App.vue
ljwx-platform-admin/src/router/index.ts
ljwx-platform-admin/src/stores/user.ts
ljwx-platform-admin/src/stores/app.ts
ljwx-platform-admin/src/api/request.ts
ljwx-platform-admin/src/api/auth.ts
ljwx-platform-admin/src/layouts/DefaultLayout.vue
ljwx-platform-admin/src/layouts/components/Sidebar.vue
ljwx-platform-admin/src/layouts/components/Navbar.vue
ljwx-platform-admin/src/views/login/index.vue
ljwx-platform-admin/src/views/dashboard/index.vue
ljwx-platform-admin/src/styles/index.scss
ljwx-platform-admin/src/styles/variables.scss
```
## 验收条件
1. package.json 所有依赖版本用 `~`,无 `^`
2. .env 文件使用 `VITE_APP_BASE_API`
3. router 使用 vue-router @5 API
4. user store 包含 accessToken / refreshToken / login / logout / refreshToken 方法
5. Axios 封装含 401 刷新队列逻辑
6. `pnpm run build:admin` 通过(先执行 build:shared)
+55
View File
@@ -0,0 +1,55 @@
---
phase: 13
title: "Admin CRUD Pages"
targets:
backend: false
frontend: true
depends_on: [12]
bundle_with: []
scope:
- "ljwx-platform-admin/src/api/**"
- "ljwx-platform-admin/src/views/**"
---
# Phase 13: Admin CRUD Pages
## 读取清单
- `CLAUDE.md`(自动加载)
- `spec/03-api.md` — 全部路由表(Users / Roles / Tenants / Dicts / Configs / Logs / Files / Notices)
- `spec/01-constraints.md` — §TypeScript 约束
- `spec/08-output-rules.md`
## 任务
Admin 管理后台所有 CRUD 页面:用户 / 角色 / 租户 / 字典 / 配置 / 日志 / 文件 / 通知管理。
## Phase-Local Manifest
```
ljwx-platform-admin/src/api/user.ts
ljwx-platform-admin/src/api/role.ts
ljwx-platform-admin/src/api/tenant.ts
ljwx-platform-admin/src/api/dict.ts
ljwx-platform-admin/src/api/config.ts
ljwx-platform-admin/src/api/log.ts
ljwx-platform-admin/src/api/file.ts
ljwx-platform-admin/src/api/notice.ts
ljwx-platform-admin/src/api/job.ts
ljwx-platform-admin/src/views/system/user/index.vue
ljwx-platform-admin/src/views/system/role/index.vue
ljwx-platform-admin/src/views/system/tenant/index.vue
ljwx-platform-admin/src/views/system/dict/index.vue
ljwx-platform-admin/src/views/system/config/index.vue
ljwx-platform-admin/src/views/system/job/index.vue
ljwx-platform-admin/src/views/monitor/operlog/index.vue
ljwx-platform-admin/src/views/monitor/loginlog/index.vue
ljwx-platform-admin/src/views/system/file/index.vue
ljwx-platform-admin/src/views/system/notice/index.vue
```
## 验收条件
1. 每个 API 文件有完整的类型定义,无 `any`
2. 每个页面支持列表查询、新增、编辑、删除(按路由表对应的操作)
3. 所有请求路径与 spec/03-api.md 一致
4. type-check 通过
+48
View File
@@ -0,0 +1,48 @@
---
phase: 14
title: "Admin Permission and Polish"
targets:
backend: false
frontend: true
depends_on: [13]
bundle_with: []
scope:
- "ljwx-platform-admin/src/router/guards.ts"
- "ljwx-platform-admin/src/stores/permission.ts"
- "ljwx-platform-admin/src/composables/usePermission.ts"
- "ljwx-platform-admin/src/composables/useTheme.ts"
- "ljwx-platform-admin/src/layouts/components/Breadcrumb.vue"
- "ljwx-platform-admin/src/layouts/components/TagsView.vue"
- "ljwx-platform-admin/src/layouts/components/ThemeSwitch.vue"
---
# Phase 14: Admin Permission & Polish
## 读取清单
- `CLAUDE.md`(自动加载)
- `spec/01-constraints.md` — §RBAC 权限
- `spec/08-output-rules.md`
## 任务
动态路由权限过滤、面包屑、标签页、主题切换。
## Phase-Local Manifest
```
ljwx-platform-admin/src/router/guards.ts
ljwx-platform-admin/src/stores/permission.ts
ljwx-platform-admin/src/composables/usePermission.ts
ljwx-platform-admin/src/layouts/components/Breadcrumb.vue
ljwx-platform-admin/src/layouts/components/TagsView.vue
ljwx-platform-admin/src/layouts/components/ThemeSwitch.vue
ljwx-platform-admin/src/composables/useTheme.ts
```
## 验收条件
1. 路由守卫根据用户权限动态过滤菜单
2. 无权限的路由不可访问
3. 面包屑、标签页正常显示
4. 主题切换(亮色/暗色)功能正常
5. build 通过
+53
View File
@@ -0,0 +1,53 @@
---
phase: 15
title: "Mobile Scaffold"
targets:
backend: false
frontend: true
depends_on: [11]
bundle_with: [16]
scope:
- "ljwx-platform-mobile/**"
---
# Phase 15: Mobile Scaffold
## 读取清单
- `CLAUDE.md`(自动加载)
- `spec/06-frontend-config.md` — §Mobile package.json、§Mobile pages.json
- `spec/01-constraints.md` — §TypeScript 约束、§JWT 认证
- `spec/08-output-rules.md`
## 任务
uni-app 骨架:登录、首页、tabBar 配置。
## Phase-Local Manifest
```
ljwx-platform-mobile/package.json
ljwx-platform-mobile/manifest.json
ljwx-platform-mobile/pages.json
ljwx-platform-mobile/tsconfig.json
ljwx-platform-mobile/.env.development
ljwx-platform-mobile/.env.production
ljwx-platform-mobile/src/main.ts
ljwx-platform-mobile/src/App.vue
ljwx-platform-mobile/src/pages/login/index.vue
ljwx-platform-mobile/src/pages/home/index.vue
ljwx-platform-mobile/src/stores/user.ts
ljwx-platform-mobile/src/api/request.ts
ljwx-platform-mobile/src/api/auth.ts
```
## 验收条件
1. package.json 依赖全部用 `~`,无 `^`
2. .env 使用 `VITE_APP_BASE_API`
3. 登录页面可正常显示
4. tabBar 包含 4 个页面
5. 无 `any`
## 可 Bundle
可与 Phase 16 一起执行。
+44
View File
@@ -0,0 +1,44 @@
---
phase: 16
title: "Mobile Feature Pages"
targets:
backend: false
frontend: true
depends_on: [15]
bundle_with: [15]
scope:
- "ljwx-platform-mobile/src/pages/**"
- "ljwx-platform-mobile/src/api/notice.ts"
- "ljwx-platform-mobile/src/api/user.ts"
---
# Phase 16: Mobile Feature Pages
## 读取清单
- `CLAUDE.md`(自动加载)
- `spec/06-frontend-config.md` — §Mobile pages.json
- `spec/08-output-rules.md`
## 任务
移动端工作台、消息、个人中心页面。
## Phase-Local Manifest
```
ljwx-platform-mobile/src/pages/work/index.vue
ljwx-platform-mobile/src/pages/message/index.vue
ljwx-platform-mobile/src/pages/mine/index.vue
ljwx-platform-mobile/src/api/notice.ts
ljwx-platform-mobile/src/api/user.ts
```
## 验收条件
1. 四个 tabBar 页面均有内容
2. 无 `any`
3. build:h5 通过(如 uni-app CLI 可用)
## 可 Bundle
可与 Phase 15 一起执行。
+58
View File
@@ -0,0 +1,58 @@
---
phase: 17
title: "Screen Scaffold"
targets:
backend: false
frontend: true
depends_on: [11]
bundle_with: [18]
scope:
- "ljwx-platform-screen/**"
---
# Phase 17: Screen Scaffold
## 读取清单
- `CLAUDE.md`(自动加载)
- `spec/06-frontend-config.md` — §Screen package.json、§Screen ECharts 暗色主题、§Screen 自适应缩放
- `spec/01-constraints.md` — §TypeScript 约束
- `spec/08-output-rules.md`
## 任务
Vue 3 + ECharts + DataV 骨架:暗色主题注册、自适应缩放 composable、基础布局。
## Phase-Local Manifest
```
ljwx-platform-screen/package.json
ljwx-platform-screen/vite.config.ts
ljwx-platform-screen/tsconfig.json
ljwx-platform-screen/.env.development
ljwx-platform-screen/.env.production
ljwx-platform-screen/index.html
ljwx-platform-screen/src/main.ts
ljwx-platform-screen/src/App.vue
ljwx-platform-screen/src/router/index.ts
ljwx-platform-screen/src/composables/useScreenAdapt.ts
ljwx-platform-screen/src/utils/echarts-setup.ts
ljwx-platform-screen/src/utils/echarts-dark-theme.ts
ljwx-platform-screen/src/layouts/ScreenLayout.vue
ljwx-platform-screen/src/views/home/index.vue
ljwx-platform-screen/src/api/request.ts
ljwx-platform-screen/src/api/screen.ts
ljwx-platform-screen/src/styles/index.scss
```
## 验收条件
1. package.json 依赖全部用 `~`,无 `^`
2. .env 使用 `VITE_APP_BASE_API`
3. 暗色主题已注册
4. 自适应缩放 composable 正确(设计宽度 1920×1080)
5. 无 `any`
6. build 通过
## 可 Bundle
可与 Phase 18 一起执行。
+56
View File
@@ -0,0 +1,56 @@
---
phase: 18
title: "Screen Components"
targets:
backend: false
frontend: true
depends_on: [17]
bundle_with: [17]
scope:
- "ljwx-platform-screen/src/components/**"
- "ljwx-platform-screen/src/views/home/index.vue"
---
# Phase 18: Screen Components
## 读取清单
- `CLAUDE.md`(自动加载)
- `spec/03-api.md` — §Screen 路由
- `spec/08-output-rules.md`
## 任务
ECharts 图表组件(≥13 个)、大屏完整布局、数据对接 screen API。
## Phase-Local Manifest
```
ljwx-platform-screen/src/components/charts/BarChart.vue
ljwx-platform-screen/src/components/charts/LineChart.vue
ljwx-platform-screen/src/components/charts/PieChart.vue
ljwx-platform-screen/src/components/charts/RadarChart.vue
ljwx-platform-screen/src/components/charts/GaugeChart.vue
ljwx-platform-screen/src/components/charts/ScatterChart.vue
ljwx-platform-screen/src/components/charts/MapChart.vue
ljwx-platform-screen/src/components/charts/FunnelChart.vue
ljwx-platform-screen/src/components/charts/HeatmapChart.vue
ljwx-platform-screen/src/components/charts/TreemapChart.vue
ljwx-platform-screen/src/components/charts/SankeyChart.vue
ljwx-platform-screen/src/components/charts/WaterBallChart.vue
ljwx-platform-screen/src/components/charts/RingChart.vue
ljwx-platform-screen/src/components/widgets/NumberFlip.vue
ljwx-platform-screen/src/components/widgets/ScrollTable.vue
ljwx-platform-screen/src/views/home/index.vue
```
## 验收条件
1. ≥ 13 个图表组件
2. 每个组件接收 typed props,无 `any`
3. 大屏首页布局完整,使用 DataV 边框装饰
4. 数据对接 `/api/screen/*` 接口
5. build 通过
## 可 Bundle
可与 Phase 17 一起执行。
+63
View File
@@ -0,0 +1,63 @@
---
phase: 19
title: "Final Gate and Docs"
targets:
backend: false
frontend: false
depends_on: [18]
bundle_with: []
scope:
- "FULL_MANIFEST.txt"
- "docs/adr/**"
- "README.md"
---
# Phase 19: Final Gate & Docs
## 读取清单
- `CLAUDE.md`(自动加载)
- `spec/` 目录全部文件(本 Phase 例外,允许全量扫描)
- `spec/08-output-rules.md`
## 任务
1. 执行 gate-all.sh 全量校验
2. 生成 FULL_MANIFEST.txt(仓库所有文件的完整列表)
3. 编写 ADR 文档
4. 编写 README.md
5. 最终版本验证
## Phase-Local Manifest
```
FULL_MANIFEST.txt
docs/adr/001-module-dependency-dag.md
docs/adr/002-audit-field-interceptor.md
docs/adr/003-jwt-authorities-convention.md
docs/adr/004-frontend-semver-tilde-only.md
docs/adr/005-changelog.md
README.md
```
## 验收条件
1. `bash scripts/gates/gate-all.sh` 全部通过
2. FULL_MANIFEST.txt 包含仓库所有文件
3. gate-manifest.sh FULL 模式校验通过
4. 至少 4 个 ADR 文档
5. README.md 包含项目简介、快速启动、技术栈、目录结构
6. 所有硬规则均满足(可使用 code-reviewer subagent 验证)
## 版本验证命令
```bash
java -version # 含 21.0.10
./mvnw dependency:tree | grep spring-boot # 3.5.11
./mvnw dependency:tree | grep mybatis # 3.0.5
./mvnw dependency:tree | grep springdoc # 2.8.15
docker run --rm postgres:16.12-alpine postgres --version # 16.12
node -v # v22.22.0
pnpm -v # 10.30.1
pnpm list vue --filter ljwx-platform-admin # 3.5.28
pnpm list vite --filter ljwx-platform-admin # 7.3.x
```