feat(dev): 本地开发工具集 v0.5.6-dev-tooling
包含本地 dev 链路完整跑通的工具集(不进生产): backend: - dev_auth.py: /api/dev/login Mock 企微 OAuth(/dev/* 路由) - messages.py: dev 模式短路企微推送,避免 invalid corpid 噪音 - main.py: dev 模式启动时建 5 条 demo conversation,让前端有数据可测 frontend: - PortalSelect.vue: dev 模式 enterRole 跳完整 URL(5173/5174/5175 端口),生产仍走相对路径 infrastructure: - docker-compose.dev.yml: dev compose(包含 backend/postgres/redis) scripts(Windows PowerShell): - dev-frontend-install.ps1: 一次性装 4 个前端依赖 - dev-frontend-start.ps1: 后台起 4 个前端 dev server - dev-check-schema-drift.ps1: 对比 SQLAlchemy 模型 vs Postgres schema,漂移 exit 1 docs: - CURRENT-FOCUS.md: 项目状态看板(每次 session 维护)
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — dev 模式 schema 漂移检测 (PowerShell 版)
|
||||
# =============================================================================
|
||||
# 作用:对比 SQLAlchemy 模型 vs 实际 DB 字段,防止"模型加了字段忘写 migration"
|
||||
# 历史踩坑:
|
||||
# - 010: agents.otp_secret (修了一次)
|
||||
# - 011: conversations.impact_scope/is_blocking/emotion_state (又踩一次)
|
||||
# 用法:.\scripts\dev-check-schema-drift.ps1
|
||||
# 前置:dev 后端跑着(docker compose -f docker-compose.dev.yml up)
|
||||
# 实现:
|
||||
# 1. 在 backend 容器里跑 Python 一行反射模型 metadata
|
||||
# 2. 在 db 容器里跑 psql 拿 information_schema
|
||||
# 3. PowerShell 做差集对比输出
|
||||
# =============================================================================
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProjectRoot = $PSScriptRoot | Split-Path -Parent
|
||||
Set-Location $ProjectRoot
|
||||
|
||||
function Write-Step($msg) { Write-Host "`n$msg" -ForegroundColor Cyan }
|
||||
function Write-OK($msg) { Write-Host " ✓ $msg" -ForegroundColor Green }
|
||||
function Write-Warn($msg) { Write-Host " ⚠ $msg" -ForegroundColor Yellow }
|
||||
function Write-Err($msg) { Write-Host " ✗ $msg" -ForegroundColor Red }
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 拿 backend 容器 ID
|
||||
# --------------------------------------------------------------------------
|
||||
$BkId = docker compose --env-file .env.dev -f docker-compose.dev.yml ps -q backend
|
||||
$DbId = docker compose --env-file .env.dev -f docker-compose.dev.yml ps -q postgres
|
||||
if (-not $BkId -or -not $DbId) {
|
||||
Write-Err "dev 后端或 db 容器没跑。先跑 dev-start.ps1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Step "═══ Schema 漂移检测 ═══"
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 1) 拿模型字段(在 backend 容器里跑 Python)
|
||||
# --------------------------------------------------------------------------
|
||||
$ModelJson = docker exec $BkId python -c @'
|
||||
import json
|
||||
from app.database import Base
|
||||
import app.models # 触发模型注册到 Base.metadata
|
||||
out = {}
|
||||
for tname, tbl in Base.metadata.tables.items():
|
||||
out[tname] = sorted([c.name for c in tbl.columns])
|
||||
print(json.dumps(out, ensure_ascii=False))
|
||||
'@ 2>&1
|
||||
|
||||
# docker exec 输出会带 status warning,过滤掉
|
||||
$ModelJson = ($ModelJson | Where-Object { $_ -notmatch '^\s*$' -and $_ -notmatch 'WARN|INFO' } | Select-Object -Last 1).Trim()
|
||||
if (-not $ModelJson) {
|
||||
Write-Err "拿不到模型字段。backend 容器里 Python 跑挂了"
|
||||
docker logs $BkId --tail 10
|
||||
exit 1
|
||||
}
|
||||
|
||||
try {
|
||||
$ModelTables = $ModelJson | ConvertFrom-Json
|
||||
} catch {
|
||||
Write-Err "模型 JSON 解析失败: $ModelJson"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-OK "模型字段: $($ModelTables.PSObject.Properties.Name.Count) 张表"
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 2) 拿 DB 字段(在 db 容器里跑 psql)
|
||||
# --------------------------------------------------------------------------
|
||||
$DbSql = "SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = 'public' ORDER BY table_name, ordinal_position;"
|
||||
$DbRaw = @(docker exec $DbId psql -U wecom -d wecom_it_desk_dev -A -t -F "`t" -c $DbSql 2>&1 | Where-Object { $_ -is [string] })
|
||||
|
||||
$DbColumns = @{}
|
||||
foreach ($line in $DbRaw) {
|
||||
if ($line -isnot [string]) { continue }
|
||||
$line = $line.Trim()
|
||||
if (-not $line) { continue }
|
||||
$parts = $line -split "`t"
|
||||
if ($parts.Count -lt 2) { continue }
|
||||
$tname = $parts[0]
|
||||
$cname = $parts[1]
|
||||
if (-not $DbColumns.ContainsKey($tname)) {
|
||||
$DbColumns[$tname] = New-Object System.Collections.Generic.List[string]
|
||||
}
|
||||
$DbColumns[$tname].Add($cname)
|
||||
}
|
||||
|
||||
# 拿 alembic version
|
||||
$VersionRaw = @(docker exec $DbId psql -U wecom -d wecom_it_desk_dev -A -t -c "SELECT version_num FROM alembic_version;" 2>&1 | Where-Object { $_ -is [string] })
|
||||
$Version = ($VersionRaw | Where-Object { $_ -match '^\d|_' } | Select-Object -First 1)
|
||||
if ($Version) { $Version = $Version.Trim() }
|
||||
Write-OK "DB 字段: $($DbColumns.Keys.Count) 张表 | alembic = $Version"
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 3) 差集对比
|
||||
# --------------------------------------------------------------------------
|
||||
$Drift = $false
|
||||
$OnlyInModel = @()
|
||||
$OnlyInDb = @()
|
||||
|
||||
foreach ($tname in $ModelTables.PSObject.Properties.Name) {
|
||||
$mcols = $ModelTables.$tname
|
||||
$dcols = if ($DbColumns.ContainsKey($tname)) { $DbColumns[$tname] } else { @() }
|
||||
|
||||
foreach ($c in $mcols) {
|
||||
if ($null -eq $dcols -or -not $dcols.Contains($c)) {
|
||||
$OnlyInModel += [PSCustomObject]@{ Table = $tname; Column = $c }
|
||||
}
|
||||
}
|
||||
foreach ($c in $dcols) {
|
||||
if ($null -ne $c -and -not $mcols.Contains($c) -and $tname -ne 'alembic_version') {
|
||||
$OnlyInDb += [PSCustomObject]@{ Table = $tname; Column = $c }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 4) 输出报告
|
||||
# --------------------------------------------------------------------------
|
||||
Write-Host ""
|
||||
Write-Host "════════════════════════════════════════════════════════════════════" -ForegroundColor White
|
||||
Write-Host " alembic current = $Version" -ForegroundColor Gray
|
||||
Write-Host " 模型表数 = $($ModelTables.PSObject.Properties.Name.Count) DB 表数 = $($DbColumns.Keys.Count)" -ForegroundColor Gray
|
||||
Write-Host "════════════════════════════════════════════════════════════════════" -ForegroundColor White
|
||||
|
||||
if ($OnlyInModel.Count -gt 0) {
|
||||
$Drift = $true
|
||||
Write-Host ""
|
||||
Write-Host " ❌ 模型有,DB 缺($($OnlyInModel.Count) 个):" -ForegroundColor Red
|
||||
foreach ($x in $OnlyInModel) {
|
||||
Write-Host " - $($x.Table).$($x.Column)" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
if ($OnlyInDb.Count -gt 0) {
|
||||
$Drift = $true
|
||||
Write-Host ""
|
||||
Write-Host " ⚠️ DB 有,模型没($($OnlyInDb.Count) 个,可能遗留):" -ForegroundColor Yellow
|
||||
foreach ($x in $OnlyInDb) {
|
||||
Write-Host " - $($x.Table).$($x.Column)" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
if ($Drift) {
|
||||
Write-Host ""
|
||||
Write-Host " 💡 修法:" -ForegroundColor Cyan
|
||||
Write-Host " 1. cd backend" -ForegroundColor Gray
|
||||
Write-Host " 2. alembic revision --autogenerate -m 'sync xxx fields'" -ForegroundColor Gray
|
||||
Write-Host " 3. 检查生成的 migration 文件(review 一下,不要直接用)" -ForegroundColor Gray
|
||||
Write-Host " 4. docker exec $BkId alembic upgrade head" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host " ✅ schema 一致,无漂移" -ForegroundColor Green
|
||||
Write-Host " $($ModelTables.PSObject.Properties.Name.Count) 张表全部对齐" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host " 💡 建议:加到 Git pre-commit / dev 启动检查里" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
exit 0
|
||||
Reference in New Issue
Block a user