fix(industry): 审计收尾 — MEDIUM + LOW 全部清零
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

M-1: Industries 创建弹窗添加 cold_start_template + pain_seed_categories
M-3: industryStore console.warn → createLogger 结构化日志
B2: classify_with_industries 平局打破 + 归一化因子 3.0 文档化
S3: set_account_industries 验证移入事务内消除 TOCTOU
T1: 4 个 SaaS 请求类型添加 deny_unknown_fields
I3: store_trigger_experience Debug 格式 → signal_name 描述名
L-1: 删除 Accounts.tsx 死代码 editingIndustries
L-3: Industries.tsx filters 类型补全 source 字段
This commit is contained in:
iven
2026-04-12 20:37:48 +08:00
parent 3cff31ec03
commit f8c5a76ce6
7 changed files with 28 additions and 13 deletions

View File

@@ -204,24 +204,25 @@ pub async fn set_account_industries(
req: &SetAccountIndustriesRequest,
) -> SaasResult<Vec<AccountIndustryItem>> {
let now = chrono::Utc::now();
// 批量验证:一次查询所有行业是否存在且启用
let ids: Vec<&str> = req.industries.iter().map(|e| e.industry_id.as_str()).collect();
// 事务:验证 + DELETE + INSERT 原子执行,消除 TOCTOU
let mut tx = pool.begin().await.map_err(SaasError::Database)?;
// 验证:所有行业必须存在且启用
let valid_count: (i64,) = sqlx::query_as(
"SELECT COUNT(*) FROM industries WHERE id = ANY($1) AND status = 'active'"
)
.bind(&ids)
.fetch_one(pool)
.fetch_one(&mut *tx)
.await
.map_err(SaasError::Database)?;
if valid_count.0 != ids.len() as i64 {
tx.rollback().await.ok();
return Err(SaasError::InvalidInput("部分行业不存在或已禁用".to_string()));
}
// 事务性 DELETE + INSERT
let mut tx = pool.begin().await.map_err(SaasError::Database)?;
sqlx::query("DELETE FROM account_industries WHERE account_id = $1")
.bind(account_id)
.execute(&mut *tx)

View File

@@ -36,6 +36,7 @@ pub struct IndustryListItem {
/// 创建行业请求
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CreateIndustryRequest {
pub id: String,
pub name: String,
@@ -57,6 +58,7 @@ pub struct CreateIndustryRequest {
/// 更新行业请求
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UpdateIndustryRequest {
pub name: Option<String>,
pub icon: Option<String>,
@@ -99,12 +101,14 @@ pub struct AccountIndustryItem {
/// 设置用户行业请求
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SetAccountIndustriesRequest {
pub industries: Vec<AccountIndustryEntry>,
}
/// 用户行业条目
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AccountIndustryEntry {
pub industry_id: String,
#[serde(default)]