diff --git a/services/user-service/migrations/001_init.sql b/services/user-service/migrations/001_init.sql index 5bb9ba3..1d6de3b 100644 --- a/services/user-service/migrations/001_init.sql +++ b/services/user-service/migrations/001_init.sql @@ -1,15 +1,51 @@ --- 用户表初始化 -CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, - username VARCHAR(50) UNIQUE NOT NULL, - password_hash VARCHAR(255) NOT NULL, - email VARCHAR(100) UNIQUE, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +-- 用户主表 +CREATE TABLE IF NOT EXISTS user_main ( + id BIGSERIAL PRIMARY KEY, + deleted BOOLEAN NOT NULL DEFAULT FALSE, + createdate TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + modifydate TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP ); +-- 用户登录账号表 +CREATE TABLE IF NOT EXISTS user_login_account ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL, + account VARCHAR(100) NOT NULL, + deleted BOOLEAN NOT NULL DEFAULT FALSE, + createdate TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + modifydate TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT fk_user_login_account_user_main FOREIGN KEY (user_id) REFERENCES user_main(id) +); + +CREATE UNIQUE INDEX IF NOT EXISTS idx_user_login_account_active + ON user_login_account(account) + WHERE deleted = FALSE; + +-- 用户密码表 +CREATE TABLE IF NOT EXISTS user_login_password ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL, + password VARCHAR(255) NOT NULL, + deleted BOOLEAN NOT NULL DEFAULT FALSE, + createdate TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + modifydate TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT fk_user_login_password_user_main FOREIGN KEY (user_id) REFERENCES user_main(id) +); + +CREATE INDEX IF NOT EXISTS idx_user_login_password_user_id + ON user_login_password(user_id); + -- 插入测试用户(密码: 123456) -- bcrypt hash: $2b$12$REwMlLDCbzR4UpL6MWnzE.AacihwpFvQhGs7vDKTwwyNMb1qBWOTm -INSERT INTO users (username, password_hash, email) -VALUES ('admin', '$2b$12$REwMlLDCbzR4UpL6MWnzE.AacihwpFvQhGs7vDKTwwyNMb1qBWOTm', 'admin@example.com') -ON CONFLICT (username) DO NOTHING; +DO $$ +DECLARE + v_user_id BIGINT; +BEGIN + INSERT INTO user_main DEFAULT VALUES RETURNING id INTO v_user_id; + + INSERT INTO user_login_account (user_id, account) + VALUES (v_user_id, 'admin'); + + INSERT INTO user_login_password (user_id, password) + VALUES (v_user_id, '$2b$12$REwMlLDCbzR4UpL6MWnzE.AacihwpFvQhGs7vDKTwwyNMb1qBWOTm'); +END $$; diff --git a/services/user-service/user-login/src/main.rs b/services/user-service/user-login/src/main.rs index d1c9eb9..7ef45c8 100644 --- a/services/user-service/user-login/src/main.rs +++ b/services/user-service/user-login/src/main.rs @@ -90,9 +90,12 @@ async fn login_handler( ) -> (StatusCode, Json) { info!("Login attempt for user: {}", payload.username); - // 查询用户 + // 查询用户账号与密码 let user: Option<(String,)> = sqlx::query_as( - "SELECT password_hash FROM users WHERE username = $1" + "SELECT p.password \ + FROM user_login_account a \ + JOIN user_login_password p ON a.user_id = p.user_id \ + WHERE a.account = $1 AND a.deleted = FALSE AND p.deleted = FALSE" ) .bind(&payload.username) .fetch_optional(&state.db) diff --git a/services/user-service/user-register/src/main.rs b/services/user-service/user-register/src/main.rs index 7927a23..e5f3699 100644 --- a/services/user-service/user-register/src/main.rs +++ b/services/user-service/user-register/src/main.rs @@ -6,7 +6,6 @@ use axum::{ Router, }; use bcrypt::hash; -use chrono::Utc; use serde::{Deserialize, Serialize}; use sqlx::PgPool; use std::env; @@ -32,7 +31,7 @@ struct RegisterRequest { #[derive(Serialize)] struct RegisterResponse { success: bool, - user_id: Option, + user_id: Option, message: String, } @@ -89,13 +88,15 @@ async fn register_handler( ); } - // 检查用户名是否存在 - let existing: Option<(i32,)> = sqlx::query_as("SELECT id FROM users WHERE username = $1") + // 检查账号是否已存在 + let existing: Option<(i64,)> = sqlx::query_as( + "SELECT id FROM user_login_account WHERE account = $1 AND deleted = FALSE" + ) .bind(&payload.username) .fetch_optional(&state.db) .await .unwrap_or(None); - + if existing.is_some() { return ( StatusCode::CONFLICT, @@ -106,25 +107,7 @@ async fn register_handler( }), ); } - - // 检查邮箱是否存在 - let existing_email: Option<(i32,)> = sqlx::query_as("SELECT id FROM users WHERE email = $1") - .bind(&payload.email) - .fetch_optional(&state.db) - .await - .unwrap_or(None); - - if existing_email.is_some() { - return ( - StatusCode::CONFLICT, - Json(RegisterResponse { - success: false, - user_id: None, - message: "Email already exists".to_string(), - }), - ); - } - + // 密码哈希 let password_hash = match hash(&payload.password, bcrypt::DEFAULT_COST) { Ok(h) => h, @@ -140,22 +123,87 @@ async fn register_handler( ); } }; - - // 插入用户 - let result = sqlx::query_as::<_, (i32,)>( - "INSERT INTO users (username, password_hash, email, created_at, updated_at) - VALUES ($1, $2, $3, $4, $4) - RETURNING id" + + // 插入用户(主从表事务) + let mut tx = match state.db.begin().await { + Ok(t) => t, + Err(e) => { + warn!("Transaction start failed: {}", e); + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(RegisterResponse { + success: false, + user_id: None, + message: "Internal error".to_string(), + }), + ); + } + }; + + let result: Result<(i64,), sqlx::Error> = sqlx::query_as( + "INSERT INTO user_main DEFAULT VALUES RETURNING id" ) - .bind(&payload.username) - .bind(&password_hash) - .bind(&payload.email) - .bind(Utc::now()) - .fetch_one(&state.db) + .fetch_one(&mut *tx) .await; - - match result { - Ok((user_id,)) => { + + let user_id = match result { + Ok((id,)) => id, + Err(e) => { + warn!("Insert user_main failed: {}", e); + let _ = tx.rollback().await; + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(RegisterResponse { + success: false, + user_id: None, + message: "Registration failed".to_string(), + }), + ); + } + }; + + if let Err(e) = sqlx::query( + "INSERT INTO user_login_account (user_id, account) VALUES ($1, $2)" + ) + .bind(user_id) + .bind(&payload.username) + .execute(&mut *tx) + .await + { + warn!("Insert user_login_account failed: {}", e); + let _ = tx.rollback().await; + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(RegisterResponse { + success: false, + user_id: None, + message: "Registration failed".to_string(), + }), + ); + } + + if let Err(e) = sqlx::query( + "INSERT INTO user_login_password (user_id, password) VALUES ($1, $2)" + ) + .bind(user_id) + .bind(&password_hash) + .execute(&mut *tx) + .await + { + warn!("Insert user_login_password failed: {}", e); + let _ = tx.rollback().await; + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(RegisterResponse { + success: false, + user_id: None, + message: "Registration failed".to_string(), + }), + ); + } + + match tx.commit().await { + Ok(()) => { info!("User {} registered with id {}", payload.username, user_id); ( StatusCode::CREATED,