diff --git a/.gitignore b/.gitignore index ff47c2d77d9194f7856b1f8244ed91e6814964a5..353c2a1f922237455a8ee10e3b6581eb6b6151a4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # will have compiled files and executables debug/ target/ +.lapce/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html diff --git a/Cargo.toml b/Cargo.toml index 278efec0d340637e81cd1bd911c0915a78c4b79c..dd0116c84b149d183c78154153fb0914b4b35829 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,13 +7,14 @@ edition = "2021" [dependencies] axum="0.7.5" -tokio={version="1.37.0",features=["macros","rt-multi-thread"]} +tokio={version="1.37.0",features=["macros","rt-multi-thread","time"]} askama = {version="0.12.1"} askama_axum="0.4.0" tower-http = { version = "0.5.2", features = ["fs", "trace"] } -sqlx = { version = "0.7.4", features = ["mysql", "runtime-tokio", "macros"] } -time = "0.3.36" - +sqlx = { version = "0.7.4", features = ["mysql", "runtime-tokio", "macros", "chrono"] } +chrono = "0.4.38" +cron = "0.12.1" +tklog = "0.0.10" diff --git a/README.md b/README.md index c870904856bc8d599397a49d83096c5a7dddafc8..a9174904e033794e8734493c4212447b0b36f7e9 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,26 @@ # axum-hello #### 介绍 -使用axum 试着创建一个web app -后端框架Axum -模板渲染aksama, askama-axum -页面html,css,js + +使用axum 试着创建一个web app, 通过项目学习Rust, tokio等 + +####使用的crate +后端框架:Axum +运行时:tokio +数据库:sqlx +定时任务:cron +模板渲染:aksama, askama-axum +页面:html,css,js, JQuery, Bootstrap #### 技术细节 模板默认是放在名为templates文件夹下,此文件夹下可以有子文件夹, 使用#[template(path="./me/index.html")]绑定实体struct和模板文件。 + + +### 注意 + + +- 文件名建议中文,假如是英文的话,如果路径中使用反斜杠那么名字可能被转移,例如\test.md, \t 就被当作换行符。 +- 路径名为全路径名称, 要改 diff --git a/resource/scripts/DDL.sql b/resource/scripts/DDL.sql index 259f59819055cad8d53cf75b815ff012627d7a44..3db8d96e04fa45f0e61c98e82df0c1fb88339436 100644 --- a/resource/scripts/DDL.sql +++ b/resource/scripts/DDL.sql @@ -9,9 +9,9 @@ CREATE TABLE article ( `summary` VARCHAR(512) NOT NULL, `path` VARCHAR(128) NOT NULL, `deleted` BIT(1) DEFAULT 0, -- 更明确地指定BIT的长度,尽管默认是1 - `createOn` TIMESTAMP NOT NULL , + `createOn` DATETIME NOT NULL , `createBy` VARCHAR(32) NOT NULL, - `modifyOn` TIMESTAMP NOT NULL , + `modifyOn` DATETIME NOT NULL , `modifyBy` VARCHAR(32) NOT NULL, PRIMARY KEY(`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; @@ -21,9 +21,9 @@ CREATE TABLE category( `id` INT(11) NOT NULL AUTO_INCREMENT, `category` VARCHAR(16) NOT NULL, `deleted` BIT(1) DEFAULT 0, -- 明确BIT长度 - `createOn` TIMESTAMP NOT NULL , + `createOn` DATETIME NOT NULL , `createBy` VARCHAR(32) NOT NULL, - `modifyOn` TIMESTAMP NOT NULL , + `modifyOn` DATETIME NOT NULL , `modifyBy` VARCHAR(32) NOT NULL, PRIMARY KEY(`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/resource/scripts/Test_DML.sql b/resource/scripts/Test_DML.sql index 437c4b5be17eacae7777145df959bc9125f271c0..de095332dac9fe737c1963c1feecd106f999e3b3 100644 --- a/resource/scripts/Test_DML.sql +++ b/resource/scripts/Test_DML.sql @@ -1,2 +1,2 @@ insert article (title,categoryId,summary,`path`, deleted, createOn, createBy, modifyOn, modifyBy) -values ('test', NULL,'summary test','/blog/1.md', 0, now(),'admin', now(),'admin') \ No newline at end of file +values ('test', NULL,'summary test','/blog/1.md', 0, now(),'admin', now(),'admin'); diff --git a/resource/step. md b/resource/step.md similarity index 97% rename from resource/step. md rename to resource/step.md index 06fae4e92938a9c86b1bdb443d7c7352db3bb118..bc486640517cb7a3f0ead24b8f5d927cafe03318 100644 --- a/resource/step. md +++ b/resource/step.md @@ -1,15 +1,15 @@ -### MySql server 设置 -阿里云服务器安装mysql服务后,防火墙开启端口 -阿里云网络安全组设置端口进出规则 -不要用root用户直接登录,新建用户,并赋予权限。 - -``` --- 创建用户并设置密码 -CREATE USER 'newuser'@'%' IDENTIFIED BY 'securepassword'; - --- 授予新用户对特定数据库的所有权限 -GRANT ALL PRIVILEGES ON mydb.* TO 'newuser'@'%'; - --- 如果需要允许用户从任何地方连接,并执行FLUSH PRIVILEGES来立即生效 -FLUSH PRIVILEGES; +### MySql server 设置 +阿里云服务器安装mysql服务后,防火墙开启端口 +阿里云网络安全组设置端口进出规则 +不要用root用户直接登录,新建用户,并赋予权限。 + +``` +-- 创建用户并设置密码 +CREATE USER 'newuser'@'%' IDENTIFIED BY 'securepassword'; + +-- 授予新用户对特定数据库的所有权限 +GRANT ALL PRIVILEGES ON mydb.* TO 'newuser'@'%'; + +-- 如果需要允许用户从任何地方连接,并执行FLUSH PRIVILEGES来立即生效 +FLUSH PRIVILEGES; ``` \ No newline at end of file diff --git a/src/configuration/db_config.rs b/src/configuration/db_config.rs new file mode 100644 index 0000000000000000000000000000000000000000..c75340907879a136192f4bad9d608c67bfefd509 --- /dev/null +++ b/src/configuration/db_config.rs @@ -0,0 +1,29 @@ +use sqlx::MySqlPool; + +use std::fs::File; +use std::io::{BufRead, BufReader, Error}; + +#[derive(Clone, Debug)] +struct Configuration { + basic_path: String, + pool: MySqlPool, +} + +impl Configuration { + pub fn new() {} + + //从当前目录读取配置文件 + + pub fn read_from_configuration_file(&self) -> Self { + let configuration_path = std::env::current_dir()?.join("configuration"); + println!("configuration path: {}", &configuration_path); + let file = File::open(&configuration_path).map_err(|error| { + println!("Read configuration file failed {:?}", error); + panic!("Error occored when read configuration: {:?}", error); + }); + let buffer = BufReader::new(file?); + for line in buffer.lines() { + let line = line?; + } + } +} diff --git a/src/configuration/log_config.rs b/src/configuration/log_config.rs new file mode 100644 index 0000000000000000000000000000000000000000..03b20a12b4848a80ebb0f9cce01482acc92da59b --- /dev/null +++ b/src/configuration/log_config.rs @@ -0,0 +1,9 @@ +use tklog::{Format, LEVEL, LOG}; + +pub fn log_init() { + LOG.set_console(true) + .set_level(LEVEL::Info) + .set_format(Format::LevelFlag | Format::Time | Format::ShortFileName) + .set_cutmode_by_size("tklogsize.txt", 1 << 20, 10, true) + .set_formatter("{level}{time} {file}:{message}\n"); +} diff --git a/src/configuration/mod.rs b/src/configuration/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..7e180654337129a3c0773da108b01709574f4249 --- /dev/null +++ b/src/configuration/mod.rs @@ -0,0 +1,2 @@ + +pub mod log_config; diff --git a/src/entity/article.rs b/src/entity/article.rs index d3a54d2b28e81e824d537e514f24001b3ab94851..537f97cf811887dc4ae7ad0208619ca714401322 100644 --- a/src/entity/article.rs +++ b/src/entity/article.rs @@ -1,18 +1,45 @@ -use time::PrimitiveDateTime; +use std::fs::File; +use std::io::BufReader; +use std::io::Read; -#[derive(Clone)] -#[derive(Debug)] +use chrono::NaiveDateTime; + +#[warn(dead_code)] +#[derive(Clone, Debug, sqlx::FromRow)] pub struct Article { - id: i32, - title: String, - categoryId: i32, - summary: String, - path: String, - deleted: bool, - createdOn: PrimitiveDateTime, - createBy: String, - modifyOn: PrimitiveDateTime, - modifyBy: String, + id: i32, + title: String, + categoryId: Option, + summary: String, + path: String, + deleted: bool, + createOn: NaiveDateTime, + createBy: String, + modifyOn: NaiveDateTime, + modifyBy: String, } +impl Article { + pub async fn read_file(&self) -> String { + println!("文件路径 {}。", &self.path); + let file = match File::open(&self.path) { + Ok(file) => { + println!("成功找到文件。"); + file + } + Err(err) => { + println!("无法打开文件: {}", err); + return String::from("Sorry, 找不到此博客。"); + } + }; + let mut buf_reader = BufReader::new(file); + let mut contents = String::new(); + let _ = buf_reader.read_to_string(&mut contents); + println!("article content {}", &contents); + contents + } + pub fn get_info(self) -> (i32, String, NaiveDateTime) { + (self.id, self.title, self.createOn) + } +} diff --git a/src/entity/article_detail.rs b/src/entity/article_detail.rs new file mode 100644 index 0000000000000000000000000000000000000000..a10998ec935a70e7b650136d373f9010210c453a --- /dev/null +++ b/src/entity/article_detail.rs @@ -0,0 +1,36 @@ +use askama_axum::Template; + +use chrono::{NaiveDateTime}; + +#[derive(Clone)] +#[derive(Debug)] +#[derive(Template)] +#[template(path = "html/blog.html")] +pub struct ArticleDetail { + pub id: i32, + pub title: String, + pub categorys: Vec, + pub content: String, + pub createOn: NaiveDateTime, +} + +impl ArticleDetail { + pub fn new(id: i32, title: String, categorys: Vec, createOn: NaiveDateTime) ->Self { + ArticleDetail { + id, + title, + categorys, + content: String::new(), + createOn, + } + } + + pub fn setContent(mut self, content: String) -> Self { + self.content = content; + self + } +} + + + + diff --git a/src/entity/article_summary.rs b/src/entity/article_summary.rs new file mode 100644 index 0000000000000000000000000000000000000000..35c1d0723bad880e4c2008d1b272500fb5d60c5f --- /dev/null +++ b/src/entity/article_summary.rs @@ -0,0 +1,37 @@ +use chrono::{NaiveDateTime, Utc}; + +#[derive(Clone, Debug, sqlx::FromRow)] +pub struct ArticleSummary { + pub id: i32, + pub title: String, + pub categoryId: Option, + pub summary: String, + pub path: String, + pub createOn: NaiveDateTime, + pub createBy: String, +} + +impl ArticleSummary { + pub fn new(title: &str, summary: &str, path: &str) -> Self { + ArticleSummary { + id: 0, + title: title.to_string(), + categoryId: Some(0), + summary: summary.to_string(), + path: path.to_string(), + createOn: Utc::now().naive_utc(), + createBy: "Admin".to_string(), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_new() { + let article_sumary = ArticleSummary::new("test_title", "test_summary" ,""); + assert_eq!(article_sumary.title, "test_title"); + } +} diff --git a/src/entity/article_summary_vec.rs b/src/entity/article_summary_vec.rs new file mode 100644 index 0000000000000000000000000000000000000000..557e6f3c49e5dcc2f777760983c02002e7604d99 --- /dev/null +++ b/src/entity/article_summary_vec.rs @@ -0,0 +1,28 @@ +use crate::entity::article_summary::ArticleSummary; +use askama_axum::Template; + +#[derive(Clone)] +#[derive(Debug)] +#[derive(Template)] +#[template(path = "html/blog_list.html")] +pub struct ArticleSummaryVec { + vec1: Vec, + vec2: Vec, + vec3: Vec, + page_total: i32, + page_num: i32, +} + +impl ArticleSummaryVec { + pub fn new(vec1: Vec, vec2: Vec, vec3: Vec, page_total: i32, page_num: i32) ->ArticleSummaryVec{ + ArticleSummaryVec{ + vec1, + vec2, + vec3, + page_total, + page_num, + } + } +} + + diff --git a/src/entity/category.rs b/src/entity/category.rs new file mode 100644 index 0000000000000000000000000000000000000000..7be2f9d6723e38b9056178956214a96a64ddcf53 --- /dev/null +++ b/src/entity/category.rs @@ -0,0 +1,15 @@ +use chrono::{NaiveDateTime, Utc, DateTime}; + +#[derive(Clone)] +#[derive(Debug)] +#[derive(sqlx::FromRow)] +pub struct Category { + id: i32, + category: String, + deleted: Option, + summary: String, + createOn: NaiveDateTime, + createBy: String, + modifyOn: NaiveDateTime, + modifyBy: String, +} diff --git a/src/entity/mod.rs b/src/entity/mod.rs index e4244701810429f7fce123f9b88d9c498d1b3665..7c0bf7d60d05365e67d919d40dddfa8e27c94781 100644 --- a/src/entity/mod.rs +++ b/src/entity/mod.rs @@ -1 +1,4 @@ -pub mod article; +pub mod article; +pub mod article_detail; +pub mod article_summary; +pub mod article_summary_vec; diff --git a/src/error/mod.rs b/src/error/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/job/file_extractor.rs b/src/job/file_extractor.rs new file mode 100644 index 0000000000000000000000000000000000000000..8a6c8dd3d8820f25d5112b63fc0395c10a3fe9eb --- /dev/null +++ b/src/job/file_extractor.rs @@ -0,0 +1,163 @@ +use std::fs; +use std::fs::DirEntry; +use std::fs::File; +use std::io::{BufRead, BufReader, Error, ErrorKind}; +use std::path::Path; +use std::path::PathBuf; + +use sqlx::MySql; +use sqlx::Pool; + +use tklog::{error, info}; + +use crate::entity::article_summary::ArticleSummary; + +pub struct Extractor<'a> { + _path: &'a Path, +} + +impl Extractor<'_> { + // one possible implementation of walking a directory only visiting files + pub async fn visit_dirs(path: &Path, pool: &Pool) -> Result<(), Error> { + println!("-------------visit_dirs start--------------"); + info!("-------------visit_dirs start--------------"); + let dir = Path::new(path); + if dir.is_dir() { + for entry in fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + println!("is dir, loop into sub dir"); + let _ = Self::visit_dirs(&path, pool); + } else if path.is_file() { + println!("-------------extract start--------------"); + info!("-------------extract start--------------"); + let _ = Extractor::extract(&entry, pool).await?; + } else { + error!("it is not dir or file."); + panic!("it is not dir or file."); + } + } + } + Ok(()) + } + + pub async fn extract(dir_entry: &DirEntry, pool: &Pool) -> Result<(), Error> { + let path = dir_entry.path(); + println!("the file name is {:?}", &path.file_name()); + if let Some(extension) = path.extension() { + let file_suffix = extension + .to_str() + .ok_or_else(|| Error::new(ErrorKind::InvalidData, "Invalid file suffix"))?; + println!("The file extension is: {:?}", &file_suffix); + info!("The file extension is: ", &file_suffix); + if file_suffix.to_lowercase() == "md" { + info!("-----------md file----------"); + let article_summary = Extractor::extract_aritcle_from_markdown(&path).await?; + let _ = sqlx::query( + r#"INSERT INTO article (title, summary, path, createBy, createOn, modifyBy, modifyOn) VALUES (?, ?, ?,'Admin', now(), 'Admin',now() )"#, + ) + .bind(&article_summary.title) + .bind(&article_summary.summary) + .bind(&article_summary.path) + .execute(pool).await + .expect("insert article failed"); + } + } else { + println!("Can not find markdown file."); + error!("Can not find markdown file."); + } + Ok(()) + } + + pub async fn extract_aritcle_from_markdown(path: &PathBuf) -> Result { + let file = File::open(&path).map_err(|error| { + if error.kind() == ErrorKind::NotFound { + Error::new( + ErrorKind::NotFound, + format!("The file path doesn't exist: {:?}", path), + ) + } else { + error + } + }); + let buffer = BufReader::new(file?); + let mut summary = String::new(); + let mut is_summary = false; + for line in buffer.lines() { + let line = line?; + //读取Summary内容,Summary内容是以Summary为标题(一级,二级,三级标题都可以),直到遇到下一个标题结束。 + if !is_summary && line.trim().contains("#") && line.contains("Summary") { + is_summary = true; + continue; + } else if is_summary && line.trim().contains("#") { + break; //结束读取 + } else if is_summary { + //summary 内容 + summary.push_str("\n\t"); + summary.push_str(&line.as_str()); + } + } + let archives_path = std::env::current_dir().unwrap().join("archives"); + let file_name_with_suffix = path + .file_name() + .ok_or_else(|| Error::new(ErrorKind::InvalidInput, "Invalid file name"))? + .to_str() + .ok_or_else(|| Error::new(ErrorKind::InvalidData, "File name is not valid UTF-8"))?; + let file_name_vec: Vec<&str> = file_name_with_suffix.split(".").collect(); + let file_name = file_name_vec[0]; + let article_summary = ArticleSummary::new( + file_name, + summary.as_str(), + format!( + "{}{}{}", + &archives_path.to_str().unwrap(), + "\\", + file_name_with_suffix + ) + .as_str(), + ); + println!("{:?}", &article_summary); + + //insert into database + /* + sqlx::query!( + r#"INSERT INTO articles (title, summary) VALUES (?, ?)"#, + article_summary.title, + article_summary.summary + ) + .execute(pool) + .await?; + */ + println!("insert a article summary!"); + info!("insert a article summary!"); + //move these files to Archives directory + + let _ = fs::rename(path, &article_summary.path); + let _ = fs::remove_file(path); + Ok(article_summary) + } +} + +#[cfg(test)] +mod test { + use super::*; + use sqlx::mysql::MySqlPoolOptions; + use std::path::Path; + + #[tokio::test] + async fn test_extract() { + let path = Path::new("/home/owen/code/rust_project/axum-hello/test/"); + + let pool = MySqlPoolOptions::new() + .connect("mysql://root:onny0620@localhost/blog") + .await + .expect("failed to connect database."); + + for entry in path.read_dir().expect("read_dir call failed") { + if let Ok(entry) = entry { + let _ = Extractor::extract(&entry, &pool); + } + } + } +} diff --git a/src/job/mod.rs b/src/job/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..8436450b6d78a493983709ad53c4b04b17235690 --- /dev/null +++ b/src/job/mod.rs @@ -0,0 +1,3 @@ +pub mod update_blog; + +pub mod file_extractor; diff --git a/src/job/update_blog.rs b/src/job/update_blog.rs new file mode 100644 index 0000000000000000000000000000000000000000..230c55432d4af99973dd1e5dd56fb84d6381f5f4 --- /dev/null +++ b/src/job/update_blog.rs @@ -0,0 +1,6 @@ +use cron::Schedule; + +struct RefreshJob { + path: String, + scheduler: Schedule, +} diff --git a/src/lib.rs b/src/lib.rs index 781160ff9f63ad4ae9ee9bb372232ac0f257f53a..0612770d48618ca7d3c17f50085cb45d797a2c80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,4 +4,6 @@ //pub mod traits; //pub mod common; pub mod entity; +pub mod job; +pub mod configuration; diff --git a/src/main.rs b/src/main.rs index f4d0cccf9ee726fdbdd4af9e6991878048b0cbfd..ea4fd77fb4db74a0d54a2636239e25f1bcdd4b8b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,59 +1,174 @@ -use axum::{ - routing::get, - Router, -}; - +use axum::{extract::Path, extract::State, routing::get, Router}; use sqlx::mysql::MySqlPoolOptions; +use sqlx::MySqlPool; + +use tklog::info; +pub mod configuration; +pub mod entity; +pub mod job; pub mod person; +use crate::entity::article::Article; +use crate::entity::article_detail::ArticleDetail; +use crate::entity::article_summary::ArticleSummary; +use crate::entity::article_summary_vec::ArticleSummaryVec; use crate::person::Person; - - +use crate::job::file_extractor::Extractor; #[tokio::main] async fn main() { - // build our application with a single route - - let workspace = std::env::current_dir().unwrap(); - println!("{:?}", workspace); - - let router = Router::new().route("/", get(article_detail)) - .route("/me", get(me)) - .nest_service("/assets", tower_http::services::ServeDir::new("assets")); + //初始化日志配置 + configuration::log_config::log_init(); + + let workspace = std::env::current_dir().unwrap(); + println!("{:?}", &workspace); + info!("Current workspace:: {:?}", &workspace.to_string_lossy()); + /* + let args: Vec = std::env::args().collect(); + if (args).capacity() < 2 { + //第一个参数是程序名称,第二个参数才是我们输入的参数 + eprintln!("You need to entry the basic_path"); + panic!("Lost the param basic_path"); + } + let basic_path = Arc::new(args[1].clone()); + */ + let pool = MySqlPoolOptions::new() + .connect("mysql://xx@xx/Blog") + .await + .expect("failed to connect database."); + + let router = Router::new() + .route("/", get(me)) + .route("/list/:page_num", get(article_list)) + .route("/article/:id", get(article_detail)) + .route("/me", get(me)) + .route("/article/new", get(extract_article)) + .with_state(pool) + .nest_service("/assets", tower_http::services::ServeDir::new("assets")); // run our app with hyper, listening globally on port 3000 let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); axum::serve(listener, router).await.unwrap(); - } -async fn say_hello()-> String{ - let pool = MySqlPoolOptions::new() - .connect("mysql://user:pass@localhost/blog").await.expect("failed to connect database."); - let row: (String,) = sqlx::query_as("SELECT 'hello world, Rust!'") - .fetch_one(&pool).await.expect("failed!"); - //assert_eq!(row.0, 150); +#[warn(dead_code)] +async fn say_hello(State(pool): State) -> String { + let row: (String,) = sqlx::query_as("SELECT 'hello world, Rust!'") + .fetch_one(&pool) + .await + .expect("failed!"); + //assert_eq!(row.0, 150); row.0 } -async fn article_detail(){ - println!(); - let pool = MySqlPoolOptions::new() - .connect("mysql://user:pass@localhost/blog").await.expect("failed to connect database."); - let row: (i32, String) = sqlx::query_as("SELECT id,title from article") - .fetch_one(&pool).await.expect("failed!"); - //assert_eq!(row.0, 150); - - println!("{:?},{:?}",row.0, row.1); +async fn article_list( + State(pool): State, + Path(page_num): Path, +) -> ArticleSummaryVec { + println!("page_num: {}", page_num); + info!("page_num: ", page_num); + let page_size: i32 = 9; + let offset = (page_num - 1) * page_size; + println!("the offset is {}", &offset); + info!("the offset is {}", &offset); + + let summary_vec: Vec= sqlx::query_as("SELECT id, title, categoryId, summary,path, createOn, createBy FROM article WHERE deleted=0 order by id desc limit ?, ? ") + .bind(offset) + .bind(page_size) + .fetch_all(&pool).await.expect("failed!"); + //assert_eq!(row.0, 150); + let total: i32 = sqlx::query_scalar("select count(id) from article where deleted=0") + .fetch_one(&pool) + .await + .expect("Query page total failed!"); + println!("total: {}", &total); + info!("total: ", &total); + let page_total = ((total as f64 / page_size as f64).ceil()) as i32; + println!("total pages: {}", &page_total); + info!("total pages: ", &page_total); + println!("{:?}", &summary_vec); + //info!("{:#?}", &summary_vec); + let mut vec1: Vec = vec![]; + let mut vec2: Vec = vec![]; + let mut vec3: Vec = vec![]; + for (position, article) in summary_vec.iter().enumerate() { + println!("position: {}", position); + info!("position: ", position); + + match position % 3 { + 0 => vec1.push(article.clone()), + 1 => vec2.push(article.clone()), + 2 => vec3.push(article.clone()), + _ => (), + } + } + ArticleSummaryVec::new(vec1, vec2, vec3, page_total, page_num) } -async fn me() -> Person<'static>{ - Person{name:"Owen"} +async fn article_detail(State(pool): State, Path(id): Path) -> ArticleDetail { + info!("the article id is ", &id); + + let article: Article = sqlx::query_as("SELECT id, title, categoryId, summary, path, deleted, createOn, createBy, modifyOn, modifyBy FROM article WHERE ID=?") + .bind(id) + .fetch_one(&pool).await.expect("Can not find the article detail"); + + println!("the article detail {:?}", &article); + let (article_id, article_title, article_create_on) = article.clone().get_info(); + let article_detail = ArticleDetail::new(article_id, article_title, vec![], article_create_on); + let content = article.read_file().await; + article_detail.setContent(content) } -async fn rsa(){ - //rsa::gen_keys_to_pem(); +pub async fn extract_article(State(pool): State) -> String { + let current_workspace = std::env::current_dir().unwrap().join("markdown"); + let insert_article_result = + Extractor::visit_dirs(std::path::Path::new(current_workspace.as_os_str()), &pool).await; + match insert_article_result { + Ok(_) => "Insert Article successfully.".to_string(), + Err(_) => "Insert Article failed!".to_string(), + } } +async fn me() -> Person<'static> { + Person { name: "Owen" } +} + +#[cfg(test)] +mod test { + use super::*; + use sqlx::mysql::MySqlPoolOptions; + use std::path::Path; + + #[tokio::test] + async fn test_extract() { + let path = Path::new("E:\\test\\markdown"); + + let pool = MySqlPoolOptions::new() + .connect("mysql://root:onny0620@localhost/blog") + .await + .expect("failed to connect database."); + + for entry in path.read_dir().expect("read_dir call failed") { + if let Ok(entry) = entry { + let _ = Extractor::extract(&entry, &pool); + } + } + } + + #[tokio::test] + async fn insert_db() { + info!(">>>>>>>>>>>>>>>>>insert article into db"); + //let mysql_conn = MySqlPool::connect("mysql://root:onny0620@localhost/blog").await.expect("Failed to connect to database"); + + let mysql_conn = MySqlPoolOptions::new() + .connect("mysql://root:onny0620@localhost/blog") + .await + .expect("failed to connect database."); + + let _ = sqlx::query( + r#"INSERT INTO article (title, summary, path, createBy, createOn, modifyBy, modifyOn) VALUES ('UT', '鸿蒙系统', 'E:\\test.md', 'Owwww', now(), 'YYYY',now() )"# + ).execute(&mysql_conn).await; + } +} diff --git a/templates/html/blog.html b/templates/html/blog.html new file mode 100644 index 0000000000000000000000000000000000000000..1961d795c0fd0f1d6f35796471c7688ad7c8cc32 --- /dev/null +++ b/templates/html/blog.html @@ -0,0 +1,82 @@ + + + + + + RustAll + + + + + +
+
+
+

{{title}}

+
+
+
+
+
+
+
+ +
+

© 2023 你的公司名或个人名. All rights reserved.

+
+ + + + + + diff --git "a/templates/html/blog_list (\345\211\257\346\234\254).html" "b/templates/html/blog_list (\345\211\257\346\234\254).html" new file mode 100644 index 0000000000000000000000000000000000000000..4f68376f289b607b34dcb504746e02b0568a52c9 --- /dev/null +++ "b/templates/html/blog_list (\345\211\257\346\234\254).html" @@ -0,0 +1,149 @@ + + + + + + 你的网页标题 + + + + + + +

hello

+ +
+
+
+
+ +
+
Card title
+ +

Some quick example text to build on the card title and make up the bulk of the card's content. + Some quick example text to build on the card title and make up the bulk of the card's content. +

+ Go somewhere +
+
+ +
+ +
+
Card title
+ +

Some quick example text to build on the card title and make up the bulk of the card's content. + Some quick example text to build on the card title and make up the bulk of the card's content. + Some quick example text to build on the card title and make up the bulk of the card's content. + Some quick example text to build on the card title and make up the bulk of the card's content. +

+ Go somewhere +
+
+ + +
+
+
+ +
+
Card title
+ +

Some quick example text to build on the card title and make up the bulk of the card's content. + Some quick example text to build on the card title and make up the bulk of the card's content. +

+ Go somewhere +
+
+
+
+
+ +
+
Card title
+ +

Some quick example text to build on the card title and make up the bulk of the card's content. + Some quick example text to build on the card title and make up the bulk of the card's content. + Some quick example text to build on the card title and make up the bulk of the card's content. + Some quick example text to build on the card title and make up the bulk of the card's content. + Some quick example text to build on the card title and make up the bulk of the card's content. + Some quick example text to build on the card title and make up the bulk of the card's content. +

+ Go somewhere +
+
+
+
+ + + +
+
+
+ +
+
Card title
+ +

Some quick example text to build on the card title and make up the bulk of the card's content. + Some quick example text to build on the card title and make up the bulk of the card's content. +

+ Go somewhere +
+
+
+
+
+ +
+
Card title
+ +

Some quick example text to build on the card title and make up the bulk of the card's content. + Some quick example text to build on the card title and make up the bulk of the card's content. +

+ Go somewhere +
+
+
+
+
+ +
+
Card title
+ +

Some quick example text to build on the card title and make up the bulk of the card's content. + Some quick example text to build on the card title and make up the bulk of the card's content. + Some quick example text to build on the card title and make up the bulk of the card's content. + Some quick example text to build on the card title and make up the bulk of the card's content. + Some quick example text to build on the card title and make up the bulk of the card's content. + Some quick example text to build on the card title and make up the bulk of the card's content. +

+ Go somewhere +
+
+
+
+ +
+ + + + +
+

© 2023 你的公司名或个人名. All rights reserved.

+
+ + + + + + diff --git a/templates/html/blog_list.html b/templates/html/blog_list.html index ca057fd0df8836ae7d5bf1dd168937041a3192f7..87f410ea2e5ae625bc620bf02eb0c686661bf6d8 100644 --- a/templates/html/blog_list.html +++ b/templates/html/blog_list.html @@ -1,36 +1,209 @@ - + - - - - 你的网页标题 - - - - - - -

hello

- - - -
-

© 2023 你的公司名或个人名. All rights reserved.

-
- - - - - + + + + RustAll + + + + + + +
+
+
+
+ {% for article in vec1 %} + + {% endfor %} +
+ +
+ {% for article in vec2 %} + + {% endfor %} +
+ +
+ {% for article in vec3 %} + + {% endfor %} +
+
+
+
+ +
+
+
+
+ +
+
+
+ +
+
+

© 2023 你的公司名或个人名. All rights reserved.

+
+ + + + + + diff --git a/templates/html/blog_list.html~ b/templates/html/blog_list.html~ new file mode 100644 index 0000000000000000000000000000000000000000..ba0c6f7d24e2da8c94e9eeda0549b843acbcc5c9 --- /dev/null +++ b/templates/html/blog_list.html~ @@ -0,0 +1,166 @@ + + + + + + + RustAll + + + + + + +
+
+
+
+ {% for article in vec1 %} +
+
+
{{article.title|e}}
+

{{article.summary|e}}

+ Go +
+
+ {% endfor %} +
+ +
+ {% for article in vec2 %} +
+
+
{{article.title|e}}
+

{{article.summary|e}}

+ Go +
+
+ {% endfor %} +
+ +
+ {% for article in vec3 %} +
+
+
{{article.title|e}}
+

{{article.summary|e}}

+ Go +
+
+ {% endfor %} +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ + + +
+

© 2023 你的公司名或个人名. All rights reserved.

+
+ + + + + + + + + + diff --git a/test/test.md b/test/test.md new file mode 100644 index 0000000000000000000000000000000000000000..fe89366369b5d10e9a46a4ada71ea345cdac0ac5 --- /dev/null +++ b/test/test.md @@ -0,0 +1,3 @@ +sdf + +sdfd