tdm_server_rust/runner/password_encrypt_runner.rs
1//! 明文密码批量 bcrypt 加密
2//!
3//! 对齐 Java `PasswordEncryptRunner`,启动时扫描 `membertb` 表中
4//! 所有明文密码并替换为 bcrypt 哈希。
5//!
6//! ## 幂等性
7//!
8//! 通过 `needs_encrypt()` 判断密码是否已加密:
9//! - 以 `$2a$10$` 或 `$2b$10$` 开头的视为已加密(bcrypt 标准前缀)
10//! - 长度恰好 60 的视为 bcrypt 哈希(跳过)
11
12use bcrypt::{hash, DEFAULT_COST};
13use sqlx::{MySqlPool, Row};
14
15/// 批量加密 `membertb` 中的明文密码
16///
17/// 遍历全表,对未加密的密码执行 `bcrypt::hash(pwd, DEFAULT_COST)` 替换。
18/// 已加密的密码自动跳过。
19///
20/// # 参数
21///
22/// - `pool`: 数据库连接池
23///
24/// # Errors
25///
26/// - 数据库查询/更新失败时返回错误
27/// - bcrypt 哈希计算失败时返回错误
28pub async fn run(pool: &MySqlPool) -> anyhow::Result<()> {
29 tracing::info!("开始执行密码加密...");
30 let rows = sqlx::query("SELECT Id, password FROM membertb")
31 .fetch_all(pool)
32 .await?;
33 for row in rows {
34 let id: i32 = row.get("Id");
35 let pwd: String = row.get("password");
36 if needs_encrypt(&pwd) {
37 let encoded = hash(&pwd, DEFAULT_COST)?;
38 sqlx::query("UPDATE membertb SET password = ? WHERE Id = ?")
39 .bind(&encoded)
40 .bind(id)
41 .execute(pool)
42 .await?;
43 }
44 }
45 tracing::info!("密码加密完成");
46 Ok(())
47}
48
49/// 判断密码是否为明文(需要加密)
50///
51/// 已加密密码特征:
52/// - bcrypt 哈希以 `$2a$10$` 或 `$2b$10$` 开头
53/// - bcrypt 哈希长度恰好为 60 字符
54///
55/// 不在以上范围的密码视为明文,需要加密处理。
56fn needs_encrypt(pwd: &str) -> bool {
57 !pwd.starts_with("$2a$10$")
58 && !pwd.starts_with("$2b$10$")
59 && pwd.len() != 60
60}