fix(saas): P1 审计修复 — 连接池断路器 + Worker重试 + XSS防护 + 状态机SQL解析器
P1 修复内容: - F7: health handler 连接池容量检查 (80%阈值返回503 degraded) - F9: SSE spawned task 并发限制 (Semaphore 16 permits) - F10: Key Pool 单次 JOIN 查询优化 (消除 N+1) - F12: CORS panic → 配置错误 - F14: 连接池使用率计算修正 (ratio = used*100/total) - F15: SQL 迁移解析器替换为状态机 (支持 $$, DO $body$, 存储过程) - Worker 重试机制: 失败任务通过 mpsc channel 重新入队 - DOMPurify XSS 防护 (PipelineResultPreview) - Admin V2: ErrorBoundary + SWR全局配置 + 请求优化
This commit is contained in:
@@ -90,7 +90,7 @@ async fn run_migration_files(pool: &PgPool, dir: &std::path::Path) -> SaasResult
|
||||
let filename = path.file_name().unwrap_or_default().to_string_lossy();
|
||||
tracing::info!("Running migration: {}", filename);
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
for stmt in content.split(';') {
|
||||
for stmt in split_sql_statements(&content) {
|
||||
let trimmed = stmt.trim();
|
||||
if !trimmed.is_empty() && !trimmed.starts_with("--") {
|
||||
sqlx::query(trimmed).execute(pool).await?;
|
||||
@@ -100,6 +100,150 @@ async fn run_migration_files(pool: &PgPool, dir: &std::path::Path) -> SaasResult
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 按语句分割 SQL 文件内容,正确处理:
|
||||
/// - 单引号字符串 `'...'`
|
||||
/// - 双引号标识符 `"..."`
|
||||
/// - 美元符号引用字符串 `$$...$$` 和 `$tag$...$tag$`
|
||||
/// - `--` 单行注释
|
||||
/// - `/* ... */` 块注释
|
||||
/// - `E'...'` 转义字符串
|
||||
fn split_sql_statements(sql: &str) -> Vec<String> {
|
||||
let mut statements = Vec::new();
|
||||
let mut current = String::new();
|
||||
let mut chars = sql.chars().peekable();
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'\'' => {
|
||||
// 单引号字符串
|
||||
current.push(ch);
|
||||
loop {
|
||||
match chars.next() {
|
||||
Some('\'') => {
|
||||
current.push('\'');
|
||||
// 检查是否为转义引号 ''
|
||||
if chars.peek() == Some(&'\'') {
|
||||
current.push(chars.next().unwrap());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Some(c) => current.push(c),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
'"' => {
|
||||
// 双引号标识符
|
||||
current.push(ch);
|
||||
loop {
|
||||
match chars.next() {
|
||||
Some('"') => {
|
||||
current.push('"');
|
||||
break;
|
||||
}
|
||||
Some(c) => current.push(c),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
'-' if chars.peek() == Some(&'-') => {
|
||||
// 单行注释: 跳过直到行尾
|
||||
chars.next(); // consume second '-'
|
||||
while let Some(&c) = chars.peek() {
|
||||
if c == '\n' {
|
||||
chars.next();
|
||||
current.push(c);
|
||||
break;
|
||||
}
|
||||
chars.next();
|
||||
}
|
||||
}
|
||||
'/' if chars.peek() == Some(&'*') => {
|
||||
// 块注释: 跳过直到 */
|
||||
chars.next(); // consume '*'
|
||||
current.push_str("/*");
|
||||
let mut prev = ' ';
|
||||
loop {
|
||||
match chars.next() {
|
||||
Some('/') if prev == '*' => {
|
||||
current.push('/');
|
||||
break;
|
||||
}
|
||||
Some(c) => {
|
||||
current.push(c);
|
||||
prev = c;
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
'$' => {
|
||||
// 美元符号引用: $$ 或 $tag$ ... $tag$
|
||||
current.push(ch);
|
||||
// 读取 tag (字母数字和下划线)
|
||||
let mut tag = String::new();
|
||||
while let Some(&c) = chars.peek() {
|
||||
if c == '$' || c.is_alphanumeric() || c == '_' {
|
||||
if c == '$' {
|
||||
chars.next();
|
||||
current.push(c);
|
||||
break;
|
||||
}
|
||||
chars.next();
|
||||
tag.push(c);
|
||||
current.push(c);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 如果 tag 为空,就是 $$ 格式
|
||||
let end_marker = if tag.is_empty() {
|
||||
"$$".to_string()
|
||||
} else {
|
||||
format!("${}$", tag)
|
||||
};
|
||||
// 读取直到遇到 end_marker
|
||||
let mut buf = String::new();
|
||||
loop {
|
||||
match chars.next() {
|
||||
Some(c) => {
|
||||
current.push(c);
|
||||
buf.push(c);
|
||||
if buf.len() > end_marker.len() {
|
||||
buf.remove(0);
|
||||
}
|
||||
if buf == end_marker {
|
||||
break;
|
||||
}
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
';' => {
|
||||
// 语句结束
|
||||
let trimmed = current.trim().to_string();
|
||||
if !trimmed.is_empty() {
|
||||
statements.push(trimmed);
|
||||
}
|
||||
current.clear();
|
||||
}
|
||||
_ => {
|
||||
current.push(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 最后一条语句 (可能不以分号结尾)
|
||||
let trimmed = current.trim().to_string();
|
||||
if !trimmed.is_empty() {
|
||||
statements.push(trimmed);
|
||||
}
|
||||
|
||||
statements
|
||||
}
|
||||
|
||||
/// Seed 角色数据
|
||||
async fn seed_roles(pool: &PgPool) -> SaasResult<()> {
|
||||
let now = chrono::Utc::now().to_rfc3339();
|
||||
|
||||
Reference in New Issue
Block a user