diff --git a/Cargo.lock b/Cargo.lock index d94d539..5859a54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1083,6 +1083,7 @@ dependencies = [ "htykc_models", "htyuc_models", "htyuc_remote", + "huike_push_info", "serde", "serde_json", "tokio", @@ -1242,6 +1243,7 @@ dependencies = [ "htyuc_models", "htyuc_remote", "htyws_models", + "huike_push_info", "reqwest", "serde", "serde_json", @@ -1267,6 +1269,14 @@ dependencies = [ "tracing", ] +[[package]] +name = "huike_push_info" +version = "0.1.0" +dependencies = [ + "htycommons", + "serde_json", +] + [[package]] name = "humantime" version = "2.3.0" diff --git a/Cargo.toml b/Cargo.toml index 13163ba..d9de3ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "huike_push_info", "htyws", "htyws_models", "htykc_models", @@ -23,6 +24,7 @@ authors = ["阿男 ", "buddy"] edition = "2021" [workspace.dependencies] +huike_push_info = { path = "huike_push_info" } htycommons = { git = "https://github.com/alchemy-studio/AuthCore.git" } htyuc_models = { git = "https://github.com/alchemy-studio/AuthCore.git" } htyuc_remote = { git = "https://github.com/alchemy-studio/AuthCore.git" } diff --git a/htykc/Cargo.toml b/htykc/Cargo.toml index b69c473..129d957 100644 --- a/htykc/Cargo.toml +++ b/htykc/Cargo.toml @@ -7,6 +7,7 @@ edition = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +huike_push_info = { workspace = true } htycommons = { workspace = true } htyuc_models = { workspace = true } htyuc_remote = { workspace = true } diff --git a/htykc/src/notifications.rs b/htykc/src/notifications.rs index 538a067..ae95fed 100644 --- a/htykc/src/notifications.rs +++ b/htykc/src/notifications.rs @@ -3,6 +3,7 @@ use htycommons::common::{current_local_datetime, HtyErr, HtyErrCode}; use htycommons::db::{DbState, UNREAD}; use htycommons::jwt::jwt_decode_token; use htycommons::models::*; +use huike_push_info::PushInfoExt; use htycommons::web::{AuthorizationHeader, HtyHostHeader, HtySudoerTokenHeader}; use htycommons::wx::{ ReqWxMessageData2keywordTemplate, ReqWxMessageData3Things2Template, @@ -332,7 +333,7 @@ async fn build_wx_push_message_for_create_or_update_clazz( value: val_thing1.replace( "STUDENT_NAME", push_info - .student_name + .student_name() .as_ref() .ok_or_else(|| anyhow!("student_name is required"))? .as_str(), @@ -343,7 +344,7 @@ async fn build_wx_push_message_for_create_or_update_clazz( value: val_thing2.replace( "KECHENG_NAME", push_info - .clazz_name + .clazz_name() .as_ref() .ok_or_else(|| anyhow!("clazz_name is required"))? .as_str(), @@ -354,7 +355,7 @@ async fn build_wx_push_message_for_create_or_update_clazz( value: val_thing4.replace( "TEACHER_NAME", push_info - .teacher_name + .teacher_name() .as_ref() .ok_or_else(|| anyhow!("teacher_name is required"))? .as_str(), @@ -369,7 +370,7 @@ async fn build_wx_push_message_for_create_or_update_clazz( .replace( "START_FROM", push_info - .start_from + .start_from() .as_ref() .ok_or_else(|| anyhow!("start_from is required"))? .as_str(), @@ -377,7 +378,7 @@ async fn build_wx_push_message_for_create_or_update_clazz( .replace( "END_BY", push_info - .end_by + .end_by() .as_ref() .ok_or_else(|| anyhow!("end_by is required"))? .as_str(), @@ -453,7 +454,7 @@ async fn build_wx_push_message_for_delete_clazz( value: val_thing2.replace( "STUDENT_NAME", push_info - .student_name + .student_name() .as_ref() .ok_or_else(|| anyhow!("student_name is required"))? .as_str(), @@ -464,7 +465,7 @@ async fn build_wx_push_message_for_delete_clazz( value: val_thing3.replace( "KECHENG_NAME", push_info - .clazz_name + .clazz_name() .as_ref() .ok_or_else(|| anyhow!("clazz_name is required"))? .as_str(), @@ -475,7 +476,7 @@ async fn build_wx_push_message_for_delete_clazz( value: val_thing10.replace( "TEACHER_NAME", push_info - .teacher_name + .teacher_name() .as_ref() .ok_or_else(|| anyhow!("teacher_name is required"))? .as_str(), @@ -487,7 +488,7 @@ async fn build_wx_push_message_for_delete_clazz( .replace( "START_FROM", push_info - .start_from + .start_from() .as_ref() .ok_or_else(|| anyhow!("start_from is required"))? .as_str(), @@ -495,7 +496,7 @@ async fn build_wx_push_message_for_delete_clazz( .replace( "END_BY", push_info - .end_by + .end_by() .as_ref() .ok_or_else(|| anyhow!("end_by is required"))? .as_str(), @@ -582,7 +583,7 @@ async fn build_wx_push_message_for_teacher_comment_clazz( .replace( "TEACHER_NAME", push_info - .teacher_name + .teacher_name() .as_ref() .ok_or_else(|| anyhow!("teacher_name is required"))? .as_str(), @@ -590,7 +591,7 @@ async fn build_wx_push_message_for_teacher_comment_clazz( .replace( "START_FROM", push_info - .start_from + .start_from() .as_ref() .ok_or_else(|| anyhow!("start_from is required"))? .as_str(), @@ -598,7 +599,7 @@ async fn build_wx_push_message_for_teacher_comment_clazz( .replace( "END_BY", push_info - .end_by + .end_by() .as_ref() .ok_or_else(|| anyhow!("end_by is required"))? .as_str(), @@ -606,7 +607,7 @@ async fn build_wx_push_message_for_teacher_comment_clazz( .replace( "KECHENG_NAME", push_info - .clazz_name + .clazz_name() .as_ref() .ok_or_else(|| anyhow!("clazz_name is required"))? .as_str(), diff --git a/htyws/Cargo.toml b/htyws/Cargo.toml index 25da623..a1d6451 100644 --- a/htyws/Cargo.toml +++ b/htyws/Cargo.toml @@ -7,6 +7,7 @@ edition = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +huike_push_info = { workspace = true } htycommons = { workspace = true } htyuc_models = { workspace = true } htyuc_remote = { workspace = true } diff --git a/htyws/src/notifications.rs b/htyws/src/notifications.rs index 7331fbd..b57a5b5 100644 --- a/htyws/src/notifications.rs +++ b/htyws/src/notifications.rs @@ -9,6 +9,7 @@ use htycommons::common::{current_local_datetime, HtyErr, HtyErrCode}; use htycommons::db::{extract_conn, fetch_db_conn, DbState, UNREAD}; use htycommons::jwt::jwt_decode_token; use htycommons::models::*; +use huike_push_info::PushInfoExt; use htycommons::wx::{ ReqWxMessageData2keywordTemplate, ReqWxMessageData3KeywordTemplate, ReqWxMessageData4KeywordTemplate, ReqWxMessageDataValue, ReqWxMiniProgram, ReqWxPushMessage, @@ -317,7 +318,7 @@ pub async fn raw_notify( } "create_resource_note_group" => { let resource_note_group_id = - push_info.resource_note_group_id.clone().ok_or(HtyErr { + push_info.resource_note_group_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("resource_note_group_id is none".to_string()), })?; @@ -326,8 +327,8 @@ pub async fn raw_notify( extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; - let course_name = push_info.qumu_name.clone(); - let course_section_name = push_info.qumu_section_name.clone(); + let course_name = push_info.qumu_name(); + let course_section_name = push_info.qumu_section_name(); let push_message = build_wx_push_message_for_create_resource_note_group( ¬ify_type, @@ -368,7 +369,7 @@ pub async fn raw_notify( ); } "create_piyue" => { - let piyue_id = push_info.piyue_id.clone().ok_or(HtyErr { + let piyue_id = push_info.piyue_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("piyue_id is none".to_string()), })?; @@ -410,7 +411,7 @@ pub async fn raw_notify( ); } "create_lianxi" => { - let lianxi_id = push_info.lianxi_id.clone().ok_or(HtyErr { + let lianxi_id = push_info.lianxi_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("lianxi_id is none".to_string()), })?; @@ -453,7 +454,7 @@ pub async fn raw_notify( ); } "delete_lianxi" => { - let lianxi_id = push_info.lianxi_id.clone().ok_or(HtyErr { + let lianxi_id = push_info.lianxi_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("lianxi_id is none".to_string()), })?; @@ -496,7 +497,7 @@ pub async fn raw_notify( ); } "create_daka" => { - let daka_id = push_info.daka_id.clone().ok_or(HtyErr { + let daka_id = push_info.daka_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("daka_id is none".to_string()), })?; @@ -505,7 +506,7 @@ pub async fn raw_notify( extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; let real_name = user_send_from.real_name.clone().unwrap_or_default(); - let remark = push_info.remark.clone(); + let remark = push_info.remark(); let push_message = build_wx_push_message_for_create_daka( &to_user_openid, &real_name, @@ -541,7 +542,7 @@ pub async fn raw_notify( ); } "update_daka" => { - let daka_id = push_info.daka_id.clone().ok_or(HtyErr { + let daka_id = push_info.daka_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("daka_id is none".to_string()), })?; @@ -584,7 +585,7 @@ pub async fn raw_notify( ); } "delete_daka" => { - let daka_id = push_info.daka_id.clone().ok_or(HtyErr { + let daka_id = push_info.daka_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("daka_id is none".to_string()), })?; @@ -627,7 +628,7 @@ pub async fn raw_notify( ); } "create_jihua" => { - let jihua_id = push_info.jihua_id.clone().ok_or(HtyErr { + let jihua_id = push_info.jihua_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("jihua_id is none".to_string()), })?; @@ -670,7 +671,7 @@ pub async fn raw_notify( ); } "update_jihua" => { - let jihua_id = push_info.jihua_id.clone().ok_or(HtyErr { + let jihua_id = push_info.jihua_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("jihua_id is none".to_string()), })?; @@ -713,7 +714,7 @@ pub async fn raw_notify( ); } "delete_jihua" => { - let jihua_id = push_info.jihua_id.clone().ok_or(HtyErr { + let jihua_id = push_info.jihua_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("jihua_id is none".to_string()), })?; @@ -805,7 +806,7 @@ async fn build_wx_push_message_for_student_comment_piyue( extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; - let lianxi_id = push_info.lianxi_id.clone().ok_or(HtyErr { + let lianxi_id = push_info.lianxi_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("lianxi_id is none".to_string()), })?; @@ -817,7 +818,7 @@ async fn build_wx_push_message_for_student_comment_piyue( let course_section = lianxi.find_linked_course_section(extract_conn(fetch_db_conn(&db_pool)?).deref_mut())?; - let lianxi_parent_text = if let Some(jihua_id) = &push_info.jihua_id { + let lianxi_parent_text = if let Some(jihua_id) = push_info.jihua_id().as_ref() { let jihua = Jihua::find_by_id(jihua_id, extract_conn(fetch_db_conn(&db_pool)?).deref_mut())?; format!( @@ -825,7 +826,7 @@ async fn build_wx_push_message_for_student_comment_piyue( jihua.start_date.format("%Y-%m-%d").to_string(), jihua.end_date.format("%Y-%m-%d").to_string() ) - } else if let Some(daka_id) = &push_info.daka_id { + } else if let Some(daka_id) = push_info.daka_id().as_ref() { // daka let daka = Daka::find_by_id(daka_id, extract_conn(fetch_db_conn(&db_pool)?).deref_mut())?; format!( @@ -971,7 +972,7 @@ async fn build_wx_push_message_for_student_comment_daka( extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; - let id_daka = push_info.daka_id.clone().ok_or(HtyErr { + let id_daka = push_info.daka_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("daka_id is none".to_string()), })?; @@ -1091,7 +1092,7 @@ async fn build_wx_push_message_for_student_comment_jihua( extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; - let jihua_id = push_info.jihua_id.clone().ok_or(HtyErr { + let jihua_id = push_info.jihua_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("jihua_id is none".to_string()), })?; @@ -1210,7 +1211,7 @@ async fn build_wx_push_message_for_teacher_comment_piyue( extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; - let lianxi_id = push_info.lianxi_id.clone().ok_or(HtyErr { + let lianxi_id = push_info.lianxi_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("lianxi_id is none".to_string()), })?; @@ -1226,7 +1227,7 @@ async fn build_wx_push_message_for_teacher_comment_piyue( course_section ); - let lianxi_parent_text = if let Some(jihua_id) = &push_info.jihua_id { + let lianxi_parent_text = if let Some(jihua_id) = push_info.jihua_id().as_ref() { let jihua = Jihua::find_by_id(jihua_id, extract_conn(fetch_db_conn(&db_pool)?).deref_mut())?; format!( @@ -1234,7 +1235,7 @@ async fn build_wx_push_message_for_teacher_comment_piyue( jihua.start_date.format("%Y-%m-%d").to_string(), jihua.end_date.format("%Y-%m-%d").to_string() ) - } else if let Some(daka_id) = &push_info.daka_id { + } else if let Some(daka_id) = push_info.daka_id().as_ref() { // daka let daka = Daka::find_by_id(daka_id, extract_conn(fetch_db_conn(&db_pool)?).deref_mut())?; format!( @@ -1378,7 +1379,7 @@ async fn build_wx_push_message_for_teacher_comment_jihua( extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; - let jihua_id = push_info.jihua_id.clone().ok_or(HtyErr { + let jihua_id = push_info.jihua_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("jihua_id is none".to_string()), })?; @@ -1489,7 +1490,7 @@ async fn build_wx_push_message_for_teacher_comment_daka( extract_conn(fetch_db_conn(&db_pool)?).deref_mut(), )?; - let id_daka = push_info.daka_id.clone().ok_or(HtyErr { + let id_daka = push_info.daka_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("daka_id is none".to_string()), })?; @@ -2480,9 +2481,8 @@ async fn build_wx_push_message_for_create_piyue( value: val_first.replace( "PIYUE_TITLE", in_push_info - .clone() - .first - .unwrap_or("同学,您提交的练习老师批阅啦".to_string()) + .first() + .unwrap_or_else(|| "同学,您提交的练习老师批阅啦".to_string()) .as_str(), ), }; @@ -2878,7 +2878,7 @@ fn build_req_hty_tongzhi( match notify_type.as_str() { "student_comment_piyue" => { - let lianxi_id = push_info.lianxi_id.clone().ok_or(HtyErr { + let lianxi_id = push_info.lianxi_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("lianxi_id is none".to_string()), })?; @@ -2945,7 +2945,7 @@ fn build_req_hty_tongzhi( } } "teacher_comment_piyue" => { - let lianxi_id = push_info.lianxi_id.clone().ok_or(HtyErr { + let lianxi_id = push_info.lianxi_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("lianxi_id is none".to_string()), })?; @@ -3011,7 +3011,7 @@ fn build_req_hty_tongzhi( } } "create_jihua" => { - let jihua_id = push_info.jihua_id.clone().ok_or(HtyErr { + let jihua_id = push_info.jihua_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("jihua_id is none".to_string()), })?; @@ -3039,7 +3039,7 @@ fn build_req_hty_tongzhi( content.jihua_end_at = Some(in_jihua.end_date); } "update_jihua" => { - let jihua_id = push_info.jihua_id.clone().ok_or(HtyErr { + let jihua_id = push_info.jihua_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("jihua_id is none".to_string()), })?; @@ -3067,7 +3067,7 @@ fn build_req_hty_tongzhi( content.jihua_end_at = Some(in_jihua.end_date); } "delete_jihua" => { - let jihua_id = push_info.jihua_id.clone().ok_or(HtyErr { + let jihua_id = push_info.jihua_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("jihua_id is none".to_string()), })?; @@ -3095,7 +3095,7 @@ fn build_req_hty_tongzhi( content.jihua_end_at = Some(in_jihua.end_date); } "create_daka" => { - let daka_id = push_info.daka_id.clone().ok_or(HtyErr { + let daka_id = push_info.daka_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("daka_id is none".to_string()), })?; @@ -3121,7 +3121,7 @@ fn build_req_hty_tongzhi( content.daka_duration_days = Some(in_daka.duration_days); } "update_daka" => { - let daka_id = push_info.daka_id.clone().ok_or(HtyErr { + let daka_id = push_info.daka_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("daka_id is none".to_string()), })?; @@ -3147,7 +3147,7 @@ fn build_req_hty_tongzhi( content.daka_duration_days = Some(in_daka.duration_days); } "delete_daka" => { - let daka_id = push_info.daka_id.clone().ok_or(HtyErr { + let daka_id = push_info.daka_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("daka_id is none".to_string()), })?; @@ -3173,7 +3173,7 @@ fn build_req_hty_tongzhi( content.daka_duration_days = Some(in_daka.duration_days); } "student_comment_jihua" => { - let jihua_id = push_info.jihua_id.clone().ok_or(HtyErr { + let jihua_id = push_info.jihua_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("jihua_id is none".to_string()), })?; @@ -3201,7 +3201,7 @@ fn build_req_hty_tongzhi( content.jihua_end_at = Some(in_jihua.end_date); } "teacher_comment_jihua" => { - let jihua_id = push_info.jihua_id.clone().ok_or(HtyErr { + let jihua_id = push_info.jihua_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("jihua_id is none".to_string()), })?; @@ -3229,7 +3229,7 @@ fn build_req_hty_tongzhi( content.jihua_end_at = Some(in_jihua.end_date); } "student_comment_daka" => { - let id_daka = push_info.daka_id.clone().ok_or(HtyErr { + let id_daka = push_info.daka_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("daka_id is none".to_string()), })?; @@ -3257,7 +3257,7 @@ fn build_req_hty_tongzhi( content.daka_duration_days = Some(in_daka.duration_days); } "teacher_comment_daka" => { - let id_daka = push_info.daka_id.clone().ok_or(HtyErr { + let id_daka = push_info.daka_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("jihua_id is none".to_string()), })?; @@ -3286,7 +3286,7 @@ fn build_req_hty_tongzhi( // nothing need to be stored because we already have push_info stored into hty_tongzhi. } "create_piyue" => { - let piyue_id = push_info.piyue_id.clone().ok_or(HtyErr { + let piyue_id = push_info.piyue_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("piyue_id is none".to_string()), })?; @@ -3363,7 +3363,7 @@ fn build_req_hty_tongzhi( } } "create_lianxi" => { - let lianxi_id = push_info.lianxi_id.clone().ok_or(HtyErr { + let lianxi_id = push_info.lianxi_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("lianxi_id is none".to_string()), })?; @@ -3436,7 +3436,7 @@ fn build_req_hty_tongzhi( } } "delete_lianxi" => { - let lianxi_id = push_info.lianxi_id.clone().ok_or(HtyErr { + let lianxi_id = push_info.lianxi_id().ok_or(HtyErr { code: HtyErrCode::NullErr, reason: Some("lianxi_id is none".to_string()), })?; diff --git a/huike_push_info/Cargo.toml b/huike_push_info/Cargo.toml new file mode 100644 index 0000000..aa79410 --- /dev/null +++ b/huike_push_info/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "huike_push_info" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +description = "Huike-specific accessors for htycommons::PushInfo::extra (business keys stay out of AuthCore)." + +[dependencies] +htycommons = { workspace = true } +serde_json = { workspace = true } diff --git a/huike_push_info/src/lib.rs b/huike_push_info/src/lib.rs new file mode 100644 index 0000000..c58e04b --- /dev/null +++ b/huike_push_info/src/lib.rs @@ -0,0 +1,80 @@ +//! Accessors for domain keys stored in [`htycommons::models::PushInfo::extra`]. +#![forbid(unsafe_code)] + +use htycommons::models::PushInfo; +use serde_json::{Map, Value}; + +fn value_to_opt_string(v: &Value) -> Option { + match v { + Value::Null => None, + Value::String(s) => Some(s.clone()), + Value::Number(n) => Some(n.to_string()), + Value::Bool(b) => Some(b.to_string()), + Value::Array(_) | Value::Object(_) => Some(v.to_string()), + } +} + +fn map_get_str(map: &Map, key: &str) -> Option { + map.get(key).and_then(value_to_opt_string) +} + +/// Read business / template keys from the flattened `extra` map (Jsonb round-trip compatible). +pub trait PushInfoExt { + fn extra_map(&self) -> ⤅ + + fn daka_id(&self) -> Option { + map_get_str(self.extra_map(), "daka_id") + } + fn jihua_id(&self) -> Option { + map_get_str(self.extra_map(), "jihua_id") + } + fn lianxi_id(&self) -> Option { + map_get_str(self.extra_map(), "lianxi_id") + } + fn piyue_id(&self) -> Option { + map_get_str(self.extra_map(), "piyue_id") + } + fn qumu_name(&self) -> Option { + map_get_str(self.extra_map(), "qumu_name") + } + fn qumu_section_name(&self) -> Option { + map_get_str(self.extra_map(), "qumu_section_name") + } + fn resource_note_group_id(&self) -> Option { + map_get_str(self.extra_map(), "resource_note_group_id") + } + fn remark(&self) -> Option { + map_get_str(self.extra_map(), "remark") + } + fn first(&self) -> Option { + map_get_str(self.extra_map(), "first") + } + fn reject_reason(&self) -> Option { + map_get_str(self.extra_map(), "reject_reason") + } + fn clazz_id(&self) -> Option { + map_get_str(self.extra_map(), "clazz_id").or_else(|| map_get_str(self.extra_map(), "kecheng_id")) + } + fn clazz_name(&self) -> Option { + map_get_str(self.extra_map(), "clazz_name") + .or_else(|| map_get_str(self.extra_map(), "kecheng_name")) + } + fn student_name(&self) -> Option { + map_get_str(self.extra_map(), "student_name") + } + fn teacher_name(&self) -> Option { + map_get_str(self.extra_map(), "teacher_name") + } + fn start_from(&self) -> Option { + map_get_str(self.extra_map(), "start_from") + } + fn end_by(&self) -> Option { + map_get_str(self.extra_map(), "end_by") + } +} + +impl PushInfoExt for PushInfo { + fn extra_map(&self) -> &Map { + &self.extra + } +}