Skip to content

Commit 5acab79

Browse files
author
James Zow
committed
add user service code need test check
1 parent d7ebab6 commit 5acab79

File tree

16 files changed

+234
-43
lines changed

16 files changed

+234
-43
lines changed

api/Cargo.toml

+5-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ edition = "2021"
55

66
[dependencies]
77
tokio = { version = "1.41.1", features = ["full"] }
8-
axum = "0.8.0-alpha.1"
8+
axum = "0.8.1"
9+
axum-macros = "0.5.0"
910

10-
utils = { path = "../utils" }
11+
service = { path = "../service" }
12+
utils = { path = "../utils" }
13+
domain = { path = "../domain" }

api/src/main.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
mod user;
2+
13
use axum::Router;
4+
use axum::routing::post;
25
use tokio;
36
use utils::config::load_config;
7+
use user::auth;
48

59
/// # Eairp api v2 Application Main function
610
/// @author James Zow
@@ -16,7 +20,8 @@ async fn main() {
1620
let config = load_config();
1721

1822
// init app router
19-
let app = Router::new();
23+
let app = Router::new()
24+
.route("/api/user/login", post(auth::account_login));
2025

2126
// print server info
2227
println!("Server is running on http://{}:{}", config.server.host, config.server.port);

api/src/resources/application-dev.toml

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
host = "127.0.0.1"
33
port = 8080
44

5-
[database]
5+
[mysql]
66
url = "mysql://root:123456@localhost:3306/eairp"
7-
pool_size = 10
7+
pool_size = 10
8+
9+
[redis]
10+
url = "redis://127.0.0.1"

api/src/user/auth.rs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use axum::Json;
2+
use domain::dto::user::account_login_dto::AccountLoginDTO;
3+
use domain::vo::user::user_info_vo::UserInfoVO;
4+
use service::user::user_service::{AuthError, UserService};
5+
use std::sync::Arc;
6+
use axum_macros::debug_handler;
7+
8+
#[debug_handler]
9+
pub async fn account_login(Json(payload): Json<AccountLoginDTO>) -> Result<Json<UserInfoVO>, AuthError> {
10+
let service = Arc::new(UserService::new());
11+
service.user_login(payload).await
12+
}

api/src/user/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub(crate) mod auth;

dao/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ edition = "2021"
77
tokio = { version = "1.41.1", features = ["full"] }
88
serde = { version = "1.0", features = ["derive"] }
99
sqlx = { version = "0.8.2", features = ["mysql", "runtime-tokio-native-tls"] }
10+
11+
deadpool-redis = { version = "0.18.0", features = ["serde"] }
12+
redis = { version = "0.28.2", default-features = false, features = ["tls"] }
13+
1014
serde_json = "1.0"
1115
dotenv = "0.15.0"
1216
once_cell = "1.20.2"

dao/src/db.rs

+32-5
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
1-
use sqlx::{mysql::MySqlPool, Pool, MySql};
1+
use sqlx::{mysql::MySqlPool, Pool as MysqlPool, MySql};
22
use once_cell::sync::Lazy;
33
use std::sync::Mutex;
44
use utils::config::load_config;
5+
use deadpool_redis::{Config, Pool as RedisPool, Runtime, CreatePoolError};
6+
use tokio::sync::Mutex as TokioMutex;
57

6-
static DB_POOL: Lazy<Mutex<Option<Pool<MySql>>>> = Lazy::new(|| Mutex::new(None));
8+
static DB_POOL: Lazy<TokioMutex<Option<MysqlPool<MySql>>>> = Lazy::new(|| TokioMutex::new(None));
9+
static REDIS_POOL: Lazy<TokioMutex<Option<RedisPool>>> = Lazy::new(|| TokioMutex::new(None));
710

8-
pub async fn get_db_pool() -> Result<Pool<MySql>, sqlx::Error> {
11+
pub async fn get_db_pool() -> Result<MysqlPool<MySql>, sqlx::Error> {
912
// load config
1013
let config = load_config();
11-
let mut pool_lock = DB_POOL.lock().unwrap();
14+
15+
let mut pool_lock = DB_POOL.lock().await; // Use async lock
1216

1317
// check if the pool has already been initialized
1418
if let Some(pool) = &*pool_lock {
1519
return Ok(pool.clone());
1620
}
1721

1822
// load database url from config file
19-
let database_url = config.database.url.clone();
23+
let database_url = config.mysql.url.clone();
2024

2125
// create a new pool
2226
let pool = MySqlPool::connect(&database_url).await?;
@@ -26,3 +30,26 @@ pub async fn get_db_pool() -> Result<Pool<MySql>, sqlx::Error> {
2630

2731
Ok(pool)
2832
}
33+
34+
pub async fn get_redis_pool() -> Result<RedisPool, CreatePoolError> {
35+
// load config
36+
let config = load_config();
37+
let mut pool_lock = REDIS_POOL.lock().await; // Use async lock
38+
39+
// check if the pool has already been initialized
40+
if let Some(pool) = &*pool_lock {
41+
return Ok(pool.clone());
42+
}
43+
44+
// load redis url from config file
45+
let redis_url = config.redis.url.clone();
46+
47+
// create a new redis client and connection manager
48+
let cfg = Config::from_url(redis_url);
49+
let pool = cfg.create_pool(Some(Runtime::Tokio1))?;
50+
51+
// set the pool in the static variable
52+
*pool_lock = Some(pool.clone());
53+
54+
Ok(pool)
55+
}

dao/src/user/user_repository.rs

+14-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
1-
use sqlx::MySqlPool;
1+
use sqlx::{query_as, MySqlPool};
22
use domain::entity::user::user::SysUser;
3+
use crate::db::get_db_pool;
34

4-
pub async fn get_all_users(pool: &MySqlPool) -> Result<Vec<SysUser>, sqlx::Error> {
5-
let users = sqlx::query_as::<_, SysUser>("SELECT * FROM sys_user")
6-
.fetch_all(pool)
5+
pub async fn find_user_by_credentials(username: &str, password: &str) -> Result<Option<SysUser>, sqlx::Error> {
6+
// let pool = get_db_pool().await?;
7+
let pool = MySqlPool::connect("mysql://root:123456@localhost:3306/eairp").await?;
8+
let user = query_as::<_, SysUser>("SELECT * FROM sys_user WHERE user_name = ? and password = ?")
9+
.bind(username)
10+
.bind(password)
11+
.fetch_optional(&pool)
712
.await?;
813

9-
Ok(users)
14+
println!("pool: {:?}", pool);
15+
// 打印user
16+
println!("user: {:?}", user);
17+
18+
Ok(user)
1019
}
+4-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
use serde::{Deserialize, Serialize};
2-
use sqlx::FromRow;
1+
use serde::{Deserialize};
32

4-
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
3+
#[derive(Deserialize)]
54
pub struct AccountLoginDTO {
6-
pub username: Option<String>, // 用户昵称(别名 姓名)
7-
8-
pub password: Option<String>, // 职位
5+
pub username: String,
6+
pub password: String,
97
}

domain/src/vo/user/user_info_vo.rs

-2
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,13 @@ fn serialize_long<S>(x: &Option<i64>, serializer: S) -> Result<S::Ok, S::Error>
3232
where
3333
S: Serializer,
3434
{
35-
// 以字符串形式序列化 Long 类型
3635
match *x {
3736
Some(value) => serializer.serialize_str(&value.to_string()),
3837
None => serializer.serialize_none(),
3938
}
4039
}
4140

4241
impl UserInfoVO {
43-
// 你可以手动实现一个 builder 模式,或者使用一些第三方库来简化这个过程(比如 `derive_builder`)。
4442
pub fn builder() -> UserInfoVOBuilder {
4543
UserInfoVOBuilder::default()
4644
}

service/Cargo.toml

+10-4
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,17 @@ version = "0.1.0"
44
edition = "2021"
55

66
[dependencies]
7-
tokio = { version = "1", features = ["full"] }
8-
serde = { version = "1.0", features = ["derive"] }
9-
serde_json = "1.0"
107
# 引入模块
118
domain = { path = "../domain" }
129
dao = { path = "../dao" }
1310
utils = { path = "../utils" }
14-
log = "0.4.22"
11+
12+
axum = "0.8.1"
13+
md5 = "0.7.0"
14+
aes = "0.8.4"
15+
cbc = "0.1.2"
16+
cipher = "0.4.3"
17+
base64 = "0.22.1"
18+
jsonwebtoken = "9.3.0"
19+
serde = { version = "1.0.217", features = ["derive"] }
20+
serde_json = "1.0.138"

service/src/lib.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
mod user;
1+
extern crate core;
2+
3+
pub mod user;

service/src/user/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
mod user_service;
1+
pub mod user_service;

service/src/user/user_service.rs

+123-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,54 @@
1-
use domain::entity::user;
2-
use domain::vo::user::user_info_vo::UserInfoVO;
1+
use dao::user::user_repository::find_user_by_credentials;
32
use domain::dto::user::account_login_dto::AccountLoginDTO;
4-
use utils::response::Response;
3+
use domain::vo::user::user_info_vo::UserInfoVO;
4+
use base64::engine::general_purpose::STANDARD;
5+
use base64::Engine as _;
6+
use serde_json::json;
7+
use md5;
8+
use jsonwebtoken::{encode, EncodingKey, Header};
9+
use aes::Aes128;
10+
use cipher::{KeyIvInit, BlockDecryptMut};
11+
use cbc::cipher::block_padding::Pkcs7;
12+
use cbc::Decryptor;
13+
use std::error::Error;
14+
use axum::http::StatusCode;
15+
use axum::{Json, Router};
16+
use axum::response::{IntoResponse, Response};
17+
use axum::routing::post;
18+
19+
type Aes128CbcDec = Decryptor<Aes128>;
20+
21+
const LOGIN_SECURITY_KEY: &[u8] = b"QsCdA/3d8CkxZ6k5c6eA61==";
22+
const IV: &[u8; 16] = b"1234567890123456"; // 请根据实际情况调整 IV
23+
24+
#[derive(serde::Serialize)]
25+
struct Claims {
26+
sub: String,
27+
exp: usize,
28+
}
29+
30+
#[derive(Debug)]
31+
pub enum AuthError {
32+
WrongCredentials,
33+
MissingCredentials,
34+
TokenCreation,
35+
InvalidToken,
36+
}
37+
38+
impl IntoResponse for AuthError {
39+
fn into_response(self) -> Response {
40+
let (status, error_message) = match self {
41+
AuthError::WrongCredentials => (StatusCode::UNAUTHORIZED, "Wrong credentials"),
42+
AuthError::MissingCredentials => (StatusCode::BAD_REQUEST, "Missing credentials"),
43+
AuthError::TokenCreation => (StatusCode::INTERNAL_SERVER_ERROR, "Token creation error"),
44+
AuthError::InvalidToken => (StatusCode::BAD_REQUEST, "Invalid token"),
45+
};
46+
let body = Json(json!({
47+
"msg": error_message,
48+
}));
49+
(status, body).into_response()
50+
}
51+
}
552

653
pub struct UserService;
754

@@ -10,7 +57,77 @@ impl UserService {
1057
UserService
1158
}
1259

13-
// pub fn user_login(&self, accountLoginDto: AccountLoginDTO) -> Result<Response<UserInfoVO>, Box<dyn std::error::Error>> {
14-
//
15-
// }
60+
pub async fn user_login(&self, account_login_dto: AccountLoginDTO) -> Result<Json<UserInfoVO>, AuthError> {
61+
// Check if username or password is empty
62+
if account_login_dto.username.is_empty() || account_login_dto.password.is_empty() {
63+
return Err(AuthError::MissingCredentials);
64+
}
65+
66+
// Decrypt the password
67+
// let encrypted_password = match STANDARD.decode(&account_login_dto.password) {
68+
// Ok(pwd) => pwd,
69+
// Err(_) => {
70+
// return Err(AuthError::WrongCredentials);
71+
// }
72+
// };
73+
//
74+
// let cipher = match Aes128CbcDec::new_from_slices(LOGIN_SECURITY_KEY, IV) {
75+
// Ok(cipher) => cipher,
76+
// Err(_) => {
77+
// return Err(AuthError::TokenCreation);
78+
// }
79+
// };
80+
81+
// let mut buffer = encrypted_password.to_vec();
82+
// let decrypted_password = match cipher.decrypt_padded_mut::<Pkcs7>(&mut buffer) {
83+
// Ok(pwd) => pwd,
84+
// Err(_) => {
85+
// return Err(AuthError::MissingCredentials);
86+
// }
87+
// };
88+
//
89+
// let decrypted_password_str = match String::from_utf8(decrypted_password.to_vec()) {
90+
// Ok(pwd) => pwd,
91+
// Err(_) => {
92+
// return Err(AuthError::MissingCredentials);
93+
// }
94+
// };
95+
96+
// Get user by username and password
97+
// let hashed_password = format!("{:x}", md5::compute(decrypted_password_str));
98+
match find_user_by_credentials(&account_login_dto.username, &*account_login_dto.password).await {
99+
Ok(Some(user)) => {
100+
let token = match create_token(&account_login_dto.username) {
101+
Ok(token) => token,
102+
Err(_) => {
103+
return Err(AuthError::MissingCredentials);
104+
}
105+
};
106+
let user_info = UserInfoVO {
107+
id: user.id,
108+
name: user.name.clone(),
109+
position: None,
110+
email: user.email.clone(),
111+
phone_number: None,
112+
description: None,
113+
user_name: None,
114+
avatar: None,
115+
system_language: None,
116+
token: Some(token),
117+
expire: Some(1694757956),
118+
};
119+
Ok(Json(user_info))
120+
}
121+
_ => Err(AuthError::MissingCredentials)
122+
}
123+
}
124+
}
125+
126+
fn create_token(username: &str) -> Result<String, Box<dyn Error>> {
127+
let my_claims = Claims {
128+
sub: username.to_owned(),
129+
exp: 10000000000,
130+
};
131+
let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret("secret".as_ref()))?;
132+
Ok(token)
16133
}

utils/src/config.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,21 @@ pub struct ServerConfig {
1010
}
1111

1212
#[derive(Debug, Deserialize)]
13-
pub struct DatabaseConfig {
13+
pub struct MySqlConfig {
1414
pub url: String,
1515
pub pool_size: u32,
1616
}
1717

18+
#[derive(Debug, Deserialize)]
19+
pub struct RedisConfig {
20+
pub url: String,
21+
}
22+
1823
#[derive(Debug, Deserialize)]
1924
pub struct AppConfig {
2025
pub server: ServerConfig,
21-
pub database: DatabaseConfig,
26+
pub mysql: MySqlConfig,
27+
pub redis: RedisConfig,
2228
}
2329

2430
pub fn load_config() -> AppConfig {

0 commit comments

Comments
 (0)