i18n增加校验,检查系统用到的多语言字段是否在csv文件中定义
This commit is contained in:
parent
f7dca5ba30
commit
ba544174b7
30
Cargo.lock
generated
30
Cargo.lock
generated
@ -292,6 +292,7 @@ dependencies = [
|
||||
name = "chuanyue-service"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"i18n",
|
||||
"library",
|
||||
"mimalloc",
|
||||
"server",
|
||||
@ -821,6 +822,12 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.9"
|
||||
@ -992,6 +999,8 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"csv",
|
||||
"lazy_static",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@ -2250,7 +2259,7 @@ checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8"
|
||||
dependencies = [
|
||||
"dotenvy",
|
||||
"either",
|
||||
"heck",
|
||||
"heck 0.4.1",
|
||||
"hex",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
@ -2397,6 +2406,25 @@ version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
|
@ -14,6 +14,7 @@ mimalloc = { workspace = true }
|
||||
|
||||
server = { path = "server" }
|
||||
library = { path = "library" }
|
||||
i18n = { path = "i18n" }
|
||||
|
||||
[workspace.dependencies]
|
||||
tokio = "1.36"
|
||||
@ -54,3 +55,5 @@ quote = "1.0.37"
|
||||
hyper = "1.4.1"
|
||||
tower = "0.5.1"
|
||||
csv = "1.3.0"
|
||||
strum = "0.26.3"
|
||||
strum_macros = "0.26.3"
|
@ -3,15 +3,15 @@ use validator::Validate;
|
||||
|
||||
#[derive(Debug, Validate, Deserialize, Serialize)]
|
||||
pub struct AuthenticateWithPassword {
|
||||
#[validate(required(message = "VALIDATE_ACCOUNT_NAME_REQUIRED"), length(min = 1, message = "VALIDATE_ACCOUNT_NAME_REQUIRED"))]
|
||||
#[validate(required(message = "ValidateAccountNameRequired"), length(min = 1, message = "ValidateAccountNameRequired"))]
|
||||
pub username: Option<String>,
|
||||
#[validate(required(message = "VALIDATE_ACCOUNT_PASSWORD_REQUIRED"), length(min = 1, message = "VALIDATE_ACCOUNT_PASSWORD_REQUIRED"))]
|
||||
#[validate(required(message = "ValidateAccountPasswordRequired"), length(min = 1, message = "ValidateAccountPasswordRequired"))]
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Validate, Deserialize, Serialize)]
|
||||
pub struct AuthenticateGooleAccountReq {
|
||||
#[validate(required(message = "VALIDATE_ACCOUNT_ID_TOKEN_REQUIRED"), length(min = 1, message = "VALIDATE_ACCOUNT_ID_TOKEN_REQUIRED"))]
|
||||
#[validate(required(message = "ValidateAccountIdTokenRequired"), length(min = 1, message = "ValidateAccountIdTokenRequired"))]
|
||||
pub id_token: Option<String>,
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,6 @@ use validator::Validate;
|
||||
|
||||
#[derive(Debug, Validate, Deserialize, Serialize)]
|
||||
pub struct FeedbackAdd {
|
||||
#[validate(required(message = "VALIDATE_FEEDBACK_CONTENT_REQUIRED"), length(min = 1, message = "VALIDATE_FEEDBACK_CONTENT_REQUIRED"))]
|
||||
#[validate(required(message = "ValidateFeedbackContentRequired"), length(min = 1, message = "ValidateFeedbackContentRequired"))]
|
||||
pub content: Option<String>
|
||||
}
|
@ -3,9 +3,9 @@ use validator::Validate;
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
pub struct PageParams {
|
||||
#[validate(required(message = "VALIDATE_PAGEABLE_PAGE_REQUIRED"), range(min = 1, message = "VALIDATE_PAGEABLE_PAGE_REQUIRED"))]
|
||||
#[validate(required(message = "ValidatePageablePageRequired"), range(min = 1, message = "ValidatePageablePageRequired"))]
|
||||
pub page: Option<i64>,
|
||||
#[serde(rename = "pageSize")]
|
||||
#[validate(required(message = "VALIDATE_PAGEABLE_PAGE_SIZE_REQUIRED"), range(min = 1, message = "VALIDATE_PAGEABLE_PAGE_SIZE_REQUIRED"))]
|
||||
#[validate(required(message = "ValidatePageablePageSizeRequired"), range(min = 1, message = "ValidatePageablePageSizeRequired"))]
|
||||
pub page_size: Option<i64>,
|
||||
}
|
26
i18n.csv
26
i18n.csv
@ -1,14 +1,14 @@
|
||||
id,en-US,zh-CN
|
||||
SERVER_INTERNAL_ERROR,Internal server error,系统内部错误
|
||||
HELLO,hello {},你好 {}
|
||||
ACCOUNT_DISABLED,account is disabled,账户已禁用
|
||||
ACCOUNT_NO_PERMISSION,account has no permission,账户无权限
|
||||
INCORRECT_USERNAME_OR_PASSWORD,incorrect username or password,用户名或密码错误
|
||||
INVALID_TOKEN,invalid token,无效令牌
|
||||
VALIDATE_FEEDBACK_CONTENT_REQUIRED,feedback content is required,反馈内容不能为空
|
||||
VALIDATE_ACCOUNT_NAME_REQUIRED,username is required,"用户名称不能为空"
|
||||
VALIDATE_ACCOUNT_PASSWORD_REQUIRED,password is required,密码不能为空
|
||||
VALIDATE_ACCOUNT_ID_TOKEN_REQUIRED,ID Token is required,用户ID Token不能为空
|
||||
VALIDATE_ACCOUNT_LANG_TAG_REQUIRED,lang tag is required,用户语言标识不能为空
|
||||
VALIDATE_PAGEABLE_PAGE_REQUIRED,invalid page number,页码无效
|
||||
VALIDATE_PAGEABLE_PAGE_SIZE_REQUIRED,invalid quantity per page,每页数量无效
|
||||
ServerInternalError,Internal server error,系统内部错误
|
||||
Hello,hello {},你好 {}
|
||||
AccountDisabled,account is disabled,账户已禁用
|
||||
AccountNoPermission,account has no permission,账户无权限
|
||||
IncorrectUsernameOrPassword,incorrect username or password,用户名或密码错误
|
||||
InvalidToken,invalid token,无效令牌
|
||||
ValidateFeedbackContentRequired,feedback content is required,反馈内容不能为空
|
||||
ValidateAccountNameRequired,username is required,"用户名称不能为空"
|
||||
ValidateAccountPasswordRequired,password is required,密码不能为空
|
||||
ValidateAccountIdTokenRequired,ID Token is required,用户ID Token不能为空
|
||||
ValidateAccountLangTagRequired,lang tag is required,用户语言标识不能为空
|
||||
ValidatePageablePageRequired,invalid page number,页码无效
|
||||
ValidatePageablePageSizeRequired,invalid quantity per page,每页数量无效
|
|
@ -7,3 +7,5 @@ edition = "2021"
|
||||
lazy_static = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
csv = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
@ -2,12 +2,15 @@ extern crate self as i18n;
|
||||
|
||||
use std::{collections::HashMap, sync::OnceLock};
|
||||
|
||||
use message_ids::MessageId;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
pub mod message_ids;
|
||||
|
||||
const I18N_FILE: &str = "i18n.csv";
|
||||
static I18N: OnceLock<HashMap<&'static str, HashMap<&'static str, &'static str>>> = OnceLock::new();
|
||||
|
||||
fn init_i18n() -> HashMap<&'static str, HashMap<&'static str, &'static str>> {
|
||||
fn init() -> HashMap<&'static str, HashMap<&'static str, &'static str>> {
|
||||
let mut i18n_map = HashMap::new();
|
||||
let mut i18n_id_map = HashMap::new();
|
||||
let mut rdr = csv::Reader::from_path(I18N_FILE).expect("读取多语言文件失败");
|
||||
@ -31,19 +34,27 @@ fn init_i18n() -> HashMap<&'static str, HashMap<&'static str, &'static str>> {
|
||||
.insert(message_id, message);
|
||||
}
|
||||
}
|
||||
tracing::info!("多语言文件解析完成:{:?}", i18n_map);
|
||||
|
||||
// 检查多语言文件是否完整
|
||||
for (lang_id, lang_map) in i18n_map.iter() {
|
||||
MessageId::iter().for_each(|message_id| {
|
||||
if !lang_map.contains_key(message_id.as_ref()) {
|
||||
tracing::error!("多语言文件缺失消息:{},语言:{}", message_id, lang_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
i18n_map
|
||||
}
|
||||
|
||||
fn get_i18n() -> &'static HashMap<&'static str, HashMap<&'static str, &'static str>> {
|
||||
I18N.get_or_init(init_i18n)
|
||||
pub fn init_i18n() -> &'static HashMap<&'static str, HashMap<&'static str, &'static str>> {
|
||||
I18N.get_or_init(init)
|
||||
}
|
||||
|
||||
/// 获取语言模板
|
||||
pub fn lang(lang_id: &str, message_id: &str) -> &'static str {
|
||||
get_i18n()
|
||||
pub fn lang(lang_id: &str, message_id: MessageId) -> &'static str {
|
||||
init_i18n()
|
||||
.get(lang_id)
|
||||
.and_then(|map| map.get(message_id))
|
||||
.and_then(|map| map.get(message_id.as_ref()))
|
||||
.unwrap_or_else(|| &"UNKNOWN")
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,19 @@
|
||||
pub const SERVER_INTERNAL_ERROR: &str = "SERVER_INTERNAL_ERROR";
|
||||
use strum_macros::{AsRefStr, Display, EnumIter, EnumString};
|
||||
|
||||
pub const HELLO: &str = "HELLO";
|
||||
pub const ACCOUNT_DISABLED: &str = "ACCOUNT_DISABLED";
|
||||
pub const ACCOUNT_NO_PERMISSION: &str = "ACCOUNT_NO_PERMISSION";
|
||||
pub const INCORRECT_USERNAME_OR_PASSWORD: &str = "INCORRECT_USERNAME_OR_PASSWORD";
|
||||
pub const INVALID_TOKEN: &str = "INVALID_TOKEN";
|
||||
#[derive(Debug, EnumIter, EnumString, Display, PartialEq, AsRefStr)]
|
||||
pub enum MessageId {
|
||||
ServerInternalError,
|
||||
Hello,
|
||||
AccountDisabled,
|
||||
AccountNoPermission,
|
||||
IncorrectUsernameOrPassword,
|
||||
InvalidToken,
|
||||
|
||||
pub const VALIDATE_FEEDBACK_CONTENT_REQUIRED: &'static str = "VALIDATE_FEEDBACK_CONTENT_REQUIRED";
|
||||
pub const VALIDATE_ACCOUNT_NAME_REQUIRED: &'static str = "VALIDATE_ACCOUNT_NAME_REQUIRED";
|
||||
pub const VALIDATE_ACCOUNT_PASSWORD_REQUIRED: &'static str = "VALIDATE_ACCOUNT_PASSWORD_REQUIRED";
|
||||
pub const VALIDATE_ACCOUNT_ID_TOKEN_REQUIRED: &'static str = "VALIDATE_ACCOUNT_ID_TOKEN_REQUIRED";
|
||||
pub const VALIDATE_ACCOUNT_LANG_TAG_REQUIRED: &'static str = "VALIDATE_ACCOUNT_LANG_TAG_REQUIRED";
|
||||
pub const VALIDATE_PAGEABLE_PAGE_REQUIRED: &'static str = "VALIDATE_PAGEABLE_PAGE_REQUIRED";
|
||||
pub const VALIDATE_PAGEABLE_PAGE_SIZE_REQUIRED: &'static str = "VALIDATE_PAGEABLE_PAGE_SIZE_REQUIRED";
|
||||
ValidateFeedbackContentRequired,
|
||||
ValidateAccountNameRequired,
|
||||
ValidateAccountPasswordRequired,
|
||||
ValidateAccountIdTokenRequired,
|
||||
ValidateAccountLangTagRequired,
|
||||
ValidatePageablePageRequired,
|
||||
ValidatePageablePageSizeRequired,
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
use i18n::message;
|
||||
use std::str::FromStr;
|
||||
|
||||
use i18n::{message, message_ids::MessageId};
|
||||
use validator::Validate;
|
||||
|
||||
use super::response::{ResData, ResErr, ResResult};
|
||||
@ -16,7 +18,7 @@ pub fn validate_params(params: &impl Validate, local: &str) -> ResResult<ResData
|
||||
err.field_errors().iter().for_each(|(_field, errs)| {
|
||||
errs.iter().for_each(|e| {
|
||||
let msg = e.message.clone().unwrap_or_default();
|
||||
errors.push(message!(local, msg.trim()))
|
||||
errors.push(message!(local, MessageId::from_str(msg.trim()).unwrap()))
|
||||
});
|
||||
});
|
||||
},
|
||||
@ -25,7 +27,7 @@ pub fn validate_params(params: &impl Validate, local: &str) -> ResResult<ResData
|
||||
err.field_errors().iter().for_each(|(_field, errs)| {
|
||||
errs.iter().for_each(|e| {
|
||||
let msg = e.message.clone().unwrap_or_default();
|
||||
errors.push(message!(local, msg.trim()))
|
||||
errors.push(message!(local, MessageId::from_str(msg.trim()).unwrap()))
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -33,7 +35,7 @@ pub fn validate_params(params: &impl Validate, local: &str) -> ResResult<ResData
|
||||
validator::ValidationErrorsKind::Field(err) => {
|
||||
err.iter().for_each(|e| {
|
||||
let msg = e.message.clone().unwrap_or_default();
|
||||
errors.push(message!(local, msg.trim()))
|
||||
errors.push(message!(local, MessageId::from_str(msg.trim()).unwrap()))
|
||||
});
|
||||
},
|
||||
};
|
||||
|
@ -5,7 +5,7 @@ use domain::dto::account::AuthenticateGooleAccountReq;
|
||||
use domain::entities::account::Account;
|
||||
use domain::vo::account::{LoginAccount, RefreshTokenResult};
|
||||
use i18n::message;
|
||||
use i18n::message_ids::{ACCOUNT_DISABLED, INVALID_TOKEN};
|
||||
use i18n::message_ids::MessageId;
|
||||
use library::cache::account_cache::{CacheAccount, LOGIN_CACHE};
|
||||
use library::context::{Context, WhiteContext};
|
||||
use library::model::response::ResErr::ErrPerm;
|
||||
@ -48,7 +48,7 @@ pub async fn authenticate_google(
|
||||
if let Some(disable_time) = account.disable_time {
|
||||
if disable_time > Utc::now() {
|
||||
tracing::error!("账户已禁用");
|
||||
return Err(ResErr::service(message!(context.get_lang_tag(), ACCOUNT_DISABLED)));
|
||||
return Err(ResErr::service(message!(context.get_lang_tag(), MessageId::AccountDisabled)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,7 +89,7 @@ pub async fn refresh_token(
|
||||
let account = context.account.clone();
|
||||
|
||||
if token::verify_refresh_token(&refresh_token).is_err() {
|
||||
return Err(ResErr::params(message!(context.get_lang_tag(), INVALID_TOKEN)));
|
||||
return Err(ResErr::params(message!(context.get_lang_tag(), MessageId::InvalidToken)));
|
||||
}
|
||||
|
||||
let refresh_token = RefreshTokenResult {
|
||||
|
@ -8,7 +8,7 @@ use domain::{
|
||||
};
|
||||
use i18n::{
|
||||
message,
|
||||
message_ids::{ACCOUNT_DISABLED, ACCOUNT_NO_PERMISSION, INCORRECT_USERNAME_OR_PASSWORD},
|
||||
message_ids::MessageId,
|
||||
};
|
||||
use library::{
|
||||
cache::account_cache::{CacheAccount, LOGIN_CACHE}, context::WhiteContext, db, model::response::{ResErr, ResResult}, token::{generate_refresh_token, generate_token}
|
||||
@ -25,7 +25,7 @@ pub async fn authenticate_with_password(
|
||||
tracing::info!("登录用户失败,用户查询为空");
|
||||
return Err(ResErr::params(message!(
|
||||
context.get_lang_tag(),
|
||||
INCORRECT_USERNAME_OR_PASSWORD
|
||||
MessageId::IncorrectUsernameOrPassword
|
||||
)));
|
||||
}
|
||||
let account = account.unwrap();
|
||||
@ -34,7 +34,7 @@ pub async fn authenticate_with_password(
|
||||
tracing::error!("账户已禁用");
|
||||
return Err(ResErr::auth(message!(
|
||||
context.get_lang_tag(),
|
||||
ACCOUNT_DISABLED
|
||||
MessageId::AccountDisabled
|
||||
)));
|
||||
}
|
||||
}
|
||||
@ -42,7 +42,7 @@ pub async fn authenticate_with_password(
|
||||
tracing::error!("账户不是管理员,无权限");
|
||||
return Err(ResErr::perm(message!(
|
||||
context.get_lang_tag(),
|
||||
ACCOUNT_NO_PERMISSION
|
||||
MessageId::AccountNoPermission
|
||||
)));
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ static GLOBAL: MiMalloc = MiMalloc;
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let (_std_guard, _file_guard) = library::core::logger::init_log(config!());
|
||||
let _i18n = i18n::init_i18n();
|
||||
library::core::db::init_database(config!()).await;
|
||||
|
||||
server::serve().await;
|
||||
|
Loading…
Reference in New Issue
Block a user