feat: initialize Nuanji (Warm Notes) project

- Base platform from base.git (ERP base: auth, core, config, message, workflow, plugin)
- Created erp-diary module skeleton (lib.rs, dto.rs, error.rs, event.rs, state.rs)
- Integrated erp-diary into workspace and erp-server
- Added DiaryModule registration in main.rs
- Added DiaryState FromRef in state.rs
- Diary routes mounted (empty routes, ready for implementation)
- Product design spec v1.2 preserved in docs/
- Implementation plan preserved in plans/

Cargo check: OK
Cargo test: OK (78+ base tests passing)
This commit is contained in:
iven
2026-05-31 20:52:19 +08:00
commit c539e6fd83
285 changed files with 59156 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
use aes_gcm::aead::Aead;
use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
use rand::RngCore;
const CIPHER_VERSION: u8 = 0x01;
/// AES-256-GCM 加密。输出格式: Base64(0x01 || nonce[12] || ciphertext + tag)
pub fn encrypt(key: &[u8; 32], plaintext: &str) -> Result<String, String> {
let cipher = Aes256Gcm::new_from_slice(key).map_err(|e| e.to_string())?;
let mut nonce_bytes = [0u8; 12];
rand::thread_rng().fill_bytes(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
let ciphertext = cipher
.encrypt(nonce, plaintext.as_bytes())
.map_err(|e| e.to_string())?;
let mut combined = vec![CIPHER_VERSION];
combined.extend_from_slice(&nonce_bytes);
combined.extend_from_slice(&ciphertext);
Ok(BASE64.encode(&combined))
}
/// AES-256-GCM 解密。支持 v1 格式: Base64(0x01 || nonce[12] || ciphertext + tag)
/// 兼容旧格式: Base64(nonce[12] || ciphertext + tag)
pub fn decrypt(key: &[u8; 32], encoded: &str) -> Result<String, String> {
let bytes = BASE64.decode(encoded).map_err(|e| e.to_string())?;
if bytes.len() < 13 {
return Err("ciphertext too short".into());
}
let (nonce_bytes, ciphertext) = if bytes[0] == CIPHER_VERSION {
// v1: version(1) + nonce(12) + ciphertext
if bytes.len() < 14 {
return Err("v1 ciphertext too short".into());
}
(&bytes[1..13], &bytes[13..])
} else {
// 旧格式: nonce(12) + ciphertext向后兼容
(&bytes[0..12], &bytes[12..])
};
let cipher = Aes256Gcm::new_from_slice(key).map_err(|e| e.to_string())?;
let nonce = Nonce::from_slice(nonce_bytes);
let plaintext = cipher
.decrypt(nonce, ciphertext)
.map_err(|e| e.to_string())?;
String::from_utf8(plaintext).map_err(|e| e.to_string())
}