ruwebframe 0.1.8

a simple webframe for rust actix-web, based on rudi and rbatis.
Documentation
// train_item_results.rs
// @Description: 统一返回结构
// @Author: raymond@163.com 时间(2026-08-18 16:49:25)
// @Update: raymond@163.com 时间(2026-08-18 16:49:25)

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::rubase::BaseEntity;
use crate::rudi::difactroy::find_bean_di_factroy;
use crate::rudomain::rudb::dbentity::find_bean_train_item_results;
use crate::rulog;

// ============================================================
// 辅助模块:实现 i64 与 JSON 字符串之间的序列化/反序列化
// 对应 Go 中的 json:"fieldName,string" 标签
// ============================================================
mod serde_i64_string {
    use serde::{self, Deserialize, Deserializer, Serializer};

    pub fn serialize<S>(value: &i64, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&value.to_string())
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<i64, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        s.parse().map_err(serde::de::Error::custom)
    }
}

mod serde_option_i64_string {
    use serde::{self, Deserialize, Deserializer, Serializer};

    pub fn serialize<S>(value: &Option<i64>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match value {
            Some(v) => serializer.serialize_str(&v.to_string()),
            None => serializer.serialize_none(),
        }
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<i64>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let opt = Option::<String>::deserialize(deserializer)?;
        match opt {
            Some(s) => s.parse().map(Some).map_err(serde::de::Error::custom),
            None => Ok(None),
        }
    }
}

// ============================================================
// 基础实体 trait(对应 Go 中的 basedto.BaseEntity)
// ============================================================
pub trait TableEntity: Send + Sync {
    fn pkey_name(&self) -> &str;
    fn pkey_value(&self) -> i64;
    fn table_name(&self) -> &str;
}
#[test]
fn test_01_serde_i64_string() {
    let r=find_bean_train_item_results().unwrap();
 
    rulog::info (r );
}
#[test]
fn test_02_serde_i64_string() {

     find_bean_di_factroy().unwrap().lock().unwrap().make_di()
}
impl BaseEntity  for TrainItemResults {

}
// ============================================================
// 核心实体:TrainItemResults
// 对应 Go 中的 TrainItemResults struct
// 内嵌了 simplemodel.Model 的字段
// ============================================================
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrainItemResults {
    // ---- 来自 simplemodel.Model ----
    #[serde(with = "serde_i64_string")]
    pub id: i64,

    #[serde(rename = "createdAt")]
    pub created_at: DateTime<Utc>,

    #[serde(rename = "updatedAt")]
    pub updated_at: DateTime<Utc>,

    #[serde(rename = "createdBy", with = "serde_i64_string")]
    pub created_by: i64,

    #[serde(rename = "updatedBy", with = "serde_i64_string")]
    pub updated_by: i64,

    // ---- TrainItemResults 特有字段 ----
    /// 学生ID
    #[serde(rename = "studentId", with = "serde_i64_string")]
    pub student_id: i64,

    /// 题库ID
    #[serde(rename = "itemBankId", with = "serde_i64_string")]
    pub item_bank_id: i64,

    /// 题目行ID
    #[serde(rename = "itemLineId", with = "serde_i64_string")]
    pub item_line_id: i64,

    /// 最近一次训练是否做对
    #[serde(rename = "ifOk")]
    pub if_ok: bool,

    /// 最后一次训练记录ID
    #[serde(rename = "trainId", with = "serde_i64_string")]
    pub train_id: i64,

    /// 最后一次完成时间
    #[serde(rename = "finishedAt")]
    pub finished_at: DateTime<Utc>,

    /// 累计训练次数
    #[serde(default)]
    pub count: i32,

    /// 累计正确次数
    #[serde(rename = "totalOk", default)]
    pub total_ok: i32,

    /// 累计错误次数
    #[serde(rename = "totalErr", default)]
    pub total_err: i32,

    /// 训练题目行ID(关联训练计划明细)
    #[serde(rename = "trainItemLine", with = "serde_i64_string")]
    pub train_item_line: i64,
}

// ============================================================
// DTO / Request / Response 包装类型
// 对应 Go 中的 TrainItemResultsDto, TrainItemResultsRequest, TrainItemResultsResp
// ============================================================
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrainItemResultsDto {
    #[serde(flatten)]
    pub inner: TrainItemResults,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrainItemResultsRequest {
    #[serde(flatten)]
    pub inner: TrainItemResults,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrainItemResultsResp {
    #[serde(flatten)]
    pub inner: TrainItemResults,
}

// ============================================================
// TrainItemResults 方法实现
// ============================================================
impl TrainItemResults {
    /// 对应 Go 的 NewTrainItemResults()
    pub fn new() -> Self {
        Self {
            id: 0,
            created_at: Utc::now(),
            updated_at: Utc::now(),
            created_by: 0,
            updated_by: 0,
            student_id: 0,
            item_bank_id: 0,
            item_line_id: 0,
            if_ok: false,
            train_id: 0,
            finished_at: Utc::now(),
            count: 0,
            total_ok: 0,
            total_err: 0,
            train_item_line: 0,
        }
    }

    /// 对应 Go 的 ModelInit() - 初始化时设置时间戳
    pub fn model_init(&mut self) {
        if self.id == 0 {
            let now = Utc::now();
            self.created_at = now;
            self.updated_at = now;
        }
    }

    /// 对应 Go 的 IfNewSave() - 判断是否为新记录
    pub fn if_new_save(&self) -> bool {
        self.id == 0
    }

    /// 对应 Go 的 ObjectKey() - 生成对象缓存键
    pub fn object_key(&self) -> String {
        let env = std::env::var("APP_ENV").unwrap_or_else(|_| "dev".to_string());
        format!("{}:db:{}:{}", env, self.table_name(), self.pkey_value())
    }

    /// 对应 Go 的 CacheKey()
    pub fn cache_key(&self) -> String {
        self.object_key()
    }

    /// 对应 Go 的 CacheKeyOf(id) - 根据指定 ID 生成缓存键
    pub fn cache_key_of(&self, id: i64) -> String {
        let env = std::env::var("APP_ENV").unwrap_or_else(|_| "dev".to_string());
        format!("{}:db:{}:{}", env, self.table_name(), id)
    }
}

// ============================================================
// BaseEntity trait 实现
// ============================================================
impl TableEntity for TrainItemResults {
    /// 对应 Go 的 PkeyName()
    fn pkey_name(&self) -> &str {
        "id"
    }

    /// 对应 Go 的 PkeyValue()
    fn pkey_value(&self) -> i64 {
        self.id
    }

    /// 对应 Go 的 TableName()
    fn table_name(&self) -> &str {
        "train_item_results"
    }
}

// ============================================================
// Default trait 实现(Rust 惯用方式替代 NewTrainItemResults)
// ============================================================
impl Default for TrainItemResults {
    fn default() -> Self {
        Self::new()
    }
}

// ============================================================
// DTO / Request / Response 的构造函数
// ============================================================
impl TrainItemResultsDto {
    /// 对应 Go 的 NewTrainItemResultsDto()
    pub fn new() -> Self {
        Self {
            inner: TrainItemResults::new(),
        }
    }
}

impl Default for TrainItemResultsDto {
    fn default() -> Self {
        Self::new()
    }
}

impl TrainItemResultsRequest {
    pub fn new() -> Self {
        Self {
            inner: TrainItemResults::new(),
        }
    }
}

impl Default for TrainItemResultsRequest {
    fn default() -> Self {
        Self::new()
    }
}

impl TrainItemResultsResp {
    pub fn new() -> Self {
        Self {
            inner: TrainItemResults::new(),
        }
    }
}

impl Default for TrainItemResultsResp {
    fn default() -> Self {
        Self::new()
    }
}

// ============================================================
// 单元测试
// ============================================================
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_new_train_item_results() {
        let result = TrainItemResults::new();
        assert_eq!(result.id, 0);
        assert_eq!(result.pkey_name(), "id");
        assert_eq!(result.table_name(), "train_item_results");
        assert!(result.if_new_save());
    }

    #[test]
    fn test_object_key() {
        let result = TrainItemResults::new();
        let key = result.object_key();
        assert!(key.contains("db"));
        assert!(key.contains("train_item_results"));
    }

    #[test]
    fn test_serialize_deserialize() {
        let mut result = TrainItemResults::new();
        result.id = 123;
        result.student_id = 456;
        result.if_ok = true;

        let json = serde_json::to_string(&result).unwrap();
        assert!(json.contains("\"id\":\"123\""));
        assert!(json.contains("\"studentId\":\"456\""));
        assert!(json.contains("\"ifOk\":true"));

        let deserialized: TrainItemResults = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized.id, 123);
        assert_eq!(deserialized.student_id, 456);
        assert_eq!(deserialized.if_ok, true);
    }
}