From 3a40bfc337f0fe13dad7f4c8c85dc673e11d21ef Mon Sep 17 00:00:00 2001 From: "owen.chao" Date: Thu, 5 Dec 2024 18:15:46 +0800 Subject: [PATCH 01/14] scheduler job --- Cargo.toml | 1 + resource/scripts/DDL.sql | 12 ++++ src/entity/article_summary.rs | 2 - src/lib.rs | 1 + src/main.rs | 6 +- .../article_read_time_scheduler_job.rs | 64 ++++++++++++++++--- src/scheduler_job/mod.rs | 1 + 7 files changed, 75 insertions(+), 12 deletions(-) create mode 100644 src/scheduler_job/mod.rs diff --git a/Cargo.toml b/Cargo.toml index fcc13a0..9f40278 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ sqlx = { version = "0.7.4", features = ["mysql", "runtime-tokio", "macros", "chr chrono = "0.4.38" cron = "0.12.1" tklog = "0.0.10" +job_scheduler = "1.2.1" serde = {version="1.0.214", features = ["derive"]} serde_json = "1.0.132" \ No newline at end of file diff --git a/resource/scripts/DDL.sql b/resource/scripts/DDL.sql index 3db8d96..9c6bf80 100644 --- a/resource/scripts/DDL.sql +++ b/resource/scripts/DDL.sql @@ -28,3 +28,15 @@ CREATE TABLE category( PRIMARY KEY(`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +-- article read count table +CREATE TABLE access_count { + `url` VARCHAR(64) NULL, + `article_id` INT(11) NULL, + `total_times` INT(11) NOT DEFAULT 0 + `create_on` DATETIME NOT NULL , + `create_by` VARCHAR(32) NOT NULL, + `modify_on` DATETIME NOT NULL , + `modify_by` VARCHAR(32) NOT NULL, +} ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + + diff --git a/src/entity/article_summary.rs b/src/entity/article_summary.rs index 0a01fb1..846a8b6 100644 --- a/src/entity/article_summary.rs +++ b/src/entity/article_summary.rs @@ -1,6 +1,4 @@ -use chrono::{NaiveDateTime, Utc}; use serde::{Deserialize, Serialize}; -use serde_json; #[derive(Clone, Debug,Deserialize, Serialize, sqlx::FromRow)] pub struct ArticleSummary { diff --git a/src/lib.rs b/src/lib.rs index 0612770..22ebdc5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,4 +6,5 @@ pub mod entity; pub mod job; pub mod configuration; +pub mod scheduler_job; diff --git a/src/main.rs b/src/main.rs index df4d366..2cafa3b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use axum::http::HeaderValue; use axum::{extract::Path, extract::State, routing::get, Router, Json}; use axum_hello::entity::article_list_view::ArticlesListView; +use axum_hello::scheduler_job::article_read_time_scheduler_job::ArticleReadTimeSchedulerJob; use tower_http::cors::{CorsLayer}; use sqlx::mysql::MySqlPoolOptions; use sqlx::MySqlPool; @@ -38,7 +39,7 @@ async fn main() { let basic_path = Arc::new(args[1].clone()); */ let pool = MySqlPoolOptions::new() - .connect("mysql://wukong:wk(2024)@localhost/Blog") + .connect("mysql://wukong:wk(2024)@47.92.236.188/Blog") .await .expect("failed to connect database."); @@ -57,6 +58,9 @@ async fn main() { .with_state(pool) .nest_service("/assets", tower_http::services::ServeDir::new("assets")); + //开启scheudler + ArticleReadTimeSchedulerJob::new(16).start(); + // run our app with hyper, listening globally on port 3000 let listener = tokio::net::TcpListener::bind("0.0.0.0:80").await.unwrap(); axum::serve(listener, router).await.unwrap(); diff --git a/src/scheduler_job/article_read_time_scheduler_job.rs b/src/scheduler_job/article_read_time_scheduler_job.rs index fde0cbd..572cafc 100644 --- a/src/scheduler_job/article_read_time_scheduler_job.rs +++ b/src/scheduler_job/article_read_time_scheduler_job.rs @@ -2,18 +2,64 @@ /// AtomicI8 原子类型 /// create: job_scheduler /// 每小时一次 -use std::sync::atomic::AtomicI8; - -use job_scheduler::Job; +/// +use std::sync::{Arc, RwLock}; +use std::thread; use std::time::Duration; -struct article_read_time_secheduler_job { - map: Vec, //我们使用index+1 来代表对应的article_id - job: Job, +use job_scheduler::{Job, JobScheduler}; + +pub struct ArticleReadTimeSchedulerJob { + count_vec: Arc>>, // 使用 Arc 允许多个所有权 } -impl article_read_time_secheduler { - pub fn new() -> JobScheduler { - JobScheduler::new() +impl ArticleReadTimeSchedulerJob { + + /// 初始化一个新的 ArticleReadTimeSchedulerJob 实例。 + /// + /// 参数: + /// - default_count_num: 文章数量,默认是 page_size * 2。 + pub fn new(default_count_num: usize) -> Self { + let count_vec = (0..default_count_num) + .map(|_| RwLock::new(0)) + .collect::>(); + + ArticleReadTimeSchedulerJob { + count_vec: Arc::new(count_vec), + } + } + + pub fn start(&self){ + let count_vec = Arc::clone(&self.count_vec); + let _ = thread::spawn(move || { + let mut sched = JobScheduler::new(); + + sched.add(Job::new("1/10 * * * * *".parse().unwrap(), || { + println!("hello job scheduler."); + for i in count_vec.iter() { + println!("{}",i.read().unwrap()); + } + })); + loop { + sched.tick(); + std::thread::sleep(Duration::from_millis(500)); + } + + }); } } + + + +#[cfg(test)] +mod test { + use super::*; + + #[test] + pub fn test_article_read_time_secheduler(){ + let job = ArticleReadTimeSchedulerJob::new(16); + job.start(); + } + +} + diff --git a/src/scheduler_job/mod.rs b/src/scheduler_job/mod.rs new file mode 100644 index 0000000..acacb9b --- /dev/null +++ b/src/scheduler_job/mod.rs @@ -0,0 +1 @@ +pub mod article_read_time_scheduler_job; -- Gitee From ca2aa771ff8d34053c6ff4d5173697c019c27716 Mon Sep 17 00:00:00 2001 From: "owen.chao" Date: Thu, 5 Dec 2024 18:58:32 +0800 Subject: [PATCH 02/14] add customize layer --- Cargo.toml | 3 +- src/layer/log_layer.rs | 50 +++++++++++++++++++ src/layer/mod.rs | 1 + src/layer/url_layper.rs | 49 ++++++++++++++++++ src/lib.rs | 1 + src/main.rs | 3 ++ .../article_read_time_scheduler_job.rs | 4 ++ 7 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 src/layer/log_layer.rs create mode 100644 src/layer/mod.rs create mode 100644 src/layer/url_layper.rs diff --git a/Cargo.toml b/Cargo.toml index 9f40278..cf1f93c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,8 @@ tokio={version="1.37.0",features=["macros","rt-multi-thread","time"]} askama = {version="0.12.1"} askama_axum="0.4.0" - +tower-layer = "0.3.3" +tower-service="0.3.3" tower-http = { version = "0.5.2", features = ["fs", "trace","cors"] } sqlx = { version = "0.7.4", features = ["mysql", "runtime-tokio", "macros", "chrono"] } diff --git a/src/layer/log_layer.rs b/src/layer/log_layer.rs new file mode 100644 index 0000000..23678e5 --- /dev/null +++ b/src/layer/log_layer.rs @@ -0,0 +1,50 @@ +use std::{fmt, task::{Context, Poll}}; + +/// a demo https://tower-rs.github.io/tower/tower_layer/trait.Layer.html +use tower_layer::Layer; +use tower_service::Service; + +#[derive(Debug, Clone)] +pub struct LogLayer { + pub target: &'static str, +} + +impl Layer for LogLayer { + type Service = LogService; + + fn layer(&self, service: S) -> Self::Service { + LogService { + target: self.target, + service + } + } +} + +// This service implements the Log behavior +#[derive(Debug, Clone)] +pub struct LogService { + target: &'static str, + service: S, +} + +impl Service for LogService +where + S: Service, + Request: fmt::Debug, +{ + type Response = S::Response; + type Error = S::Error; + type Future = S::Future; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.service.poll_ready(cx) + } + + fn call(&mut self, request: Request) -> Self::Future { + // Insert log statement here or other functionality + //是单线程还是多线程? + println!("thread id: {:?}",std::thread::current().id()); + println!("request = {:?}, target = {:?}", request, self.target); + self.service.call(request) + } +} \ No newline at end of file diff --git a/src/layer/mod.rs b/src/layer/mod.rs new file mode 100644 index 0000000..f954043 --- /dev/null +++ b/src/layer/mod.rs @@ -0,0 +1 @@ +pub mod log_layer; \ No newline at end of file diff --git a/src/layer/url_layper.rs b/src/layer/url_layper.rs new file mode 100644 index 0000000..fffe521 --- /dev/null +++ b/src/layer/url_layper.rs @@ -0,0 +1,49 @@ +use std::{fmt, task::{Context, Poll}}; + +/// a demo https://tower-rs.github.io/tower/tower_layer/trait.Layer.html +use tower_layer::Layer; +use tower_service::Service; + +#[derive(Debug, Clone)] +pub struct UrlLayer { + pub target: &'static str, +} + +impl Layer for UrlLayer { + type Service = LogService; + + fn layer(&self, service: S) -> Self::Service { + LogService { + target: self.target, + service + } + } +} + +// This service implements the Log behavior +#[derive(Debug, Clone)] +pub struct UrlService { + target: &'static str, + service: S, +} + +impl Service for UrlService +where + S: Service, + Request: fmt::Debug, +{ + type Response = S::Response; + type Error = S::Error; + type Future = S::Future; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.service.poll_ready(cx) + } + + fn call(&mut self, request: Request) -> Self::Future { + //更新文章访问次数值 + + + self.service.call(request) + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 22ebdc5..4172a6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,4 +7,5 @@ pub mod entity; pub mod job; pub mod configuration; pub mod scheduler_job; +pub mod layer; diff --git a/src/main.rs b/src/main.rs index 2cafa3b..d6e7748 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use axum::http::HeaderValue; use axum::{extract::Path, extract::State, routing::get, Router, Json}; use axum_hello::entity::article_list_view::ArticlesListView; +use axum_hello::layer::log_layer::LogLayer; use axum_hello::scheduler_job::article_read_time_scheduler_job::ArticleReadTimeSchedulerJob; use tower_http::cors::{CorsLayer}; use sqlx::mysql::MySqlPoolOptions; @@ -43,6 +44,7 @@ async fn main() { .await .expect("failed to connect database."); + let log_layer = LogLayer { target: "my_target" }; let router = Router::new() .route("/", get(articles_view)) //只返回试图 .route("/articles",get(articles_view)) //只返回试图 @@ -55,6 +57,7 @@ async fn main() { CorsLayer::new() .allow_origin("*".parse::().unwrap()) ) + .layer(log_layer) .with_state(pool) .nest_service("/assets", tower_http::services::ServeDir::new("assets")); diff --git a/src/scheduler_job/article_read_time_scheduler_job.rs b/src/scheduler_job/article_read_time_scheduler_job.rs index 572cafc..a0e73bf 100644 --- a/src/scheduler_job/article_read_time_scheduler_job.rs +++ b/src/scheduler_job/article_read_time_scheduler_job.rs @@ -29,6 +29,10 @@ impl ArticleReadTimeSchedulerJob { } } + pub fn get_count_vec(&self) { + + } + pub fn start(&self){ let count_vec = Arc::clone(&self.count_vec); let _ = thread::spawn(move || { -- Gitee From 2b4446221ad3db2a5f9c2351d58010cf074d0360 Mon Sep 17 00:00:00 2001 From: "owen.chao" Date: Thu, 5 Dec 2024 18:59:15 +0800 Subject: [PATCH 03/14] add customize layer --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index d6e7748..c97b32f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,7 +40,7 @@ async fn main() { let basic_path = Arc::new(args[1].clone()); */ let pool = MySqlPoolOptions::new() - .connect("mysql://wukong:wk(2024)@47.92.236.188/Blog") + .connect("mysql://wukong:wk(2024)@address/Blog") .await .expect("failed to connect database."); -- Gitee From 84306b36592c09181ed9994fdff638286eec701e Mon Sep 17 00:00:00 2001 From: "owen.chao" Date: Fri, 6 Dec 2024 18:32:11 +0800 Subject: [PATCH 04/14] doc --- ...213\345\255\220\345\255\246Rust-RcyuArc.md" | 18 ++++++++++++++++++ src/main.rs | 8 +++++--- .../article_read_time_scheduler_job.rs | 5 +++-- 3 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 "src/doc/\351\200\232\350\277\207\344\276\213\345\255\220\345\255\246Rust-RcyuArc.md" diff --git "a/src/doc/\351\200\232\350\277\207\344\276\213\345\255\220\345\255\246Rust-RcyuArc.md" "b/src/doc/\351\200\232\350\277\207\344\276\213\345\255\220\345\255\246Rust-RcyuArc.md" new file mode 100644 index 0000000..ac74a9a --- /dev/null +++ "b/src/doc/\351\200\232\350\277\207\344\276\213\345\255\220\345\255\246Rust-RcyuArc.md" @@ -0,0 +1,18 @@ +### Rc与Arc +#### 目的 +Rust 中的所有权机制要求一个值一个时刻只能有一个所有者。那么在这样的情形下,就会有问题: +- 图数据库中,一个节点可以有多个边指向它; +- 多线程中每个线程可能需要修改内存中同一个值; +- 同一个线程在程序的不同部分都需要修改一个值,并且无法确定哪个修改部分先执行。 +所有权机制在这样的情况下会让程序走不下去。 +Rc/Arc 就是用来解决这样的问题的, 通过引用计数的方式让同一个值可以有多个拥有者。Rc 全城Reference Counting。 Rc 适用于单线程;Arc适用于多线程。 + +通过追踪引用数量来决定这个值是否还在使用。如果这个值有0个引用,那么它将被清理。 + +#### Rc + + +#### Reference +《The Rust Programming Language》(Rc, the Reference Counted Smart Pointer)[https://doc.rust-lang.org/book/ch15-04-rc.html] +《Rust语言圣经》(Rc与Arc实现1vN所有权机制)[https://course.rs/advance/smart-pointer/rc-arc.html] + diff --git a/src/main.rs b/src/main.rs index c97b32f..f3c0adf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,10 +40,13 @@ async fn main() { let basic_path = Arc::new(args[1].clone()); */ let pool = MySqlPoolOptions::new() - .connect("mysql://wukong:wk(2024)@address/Blog") + .connect("mysql://wukong:wk(2024)@47.92.236.188/Blog") .await .expect("failed to connect database."); + //开启scheudler + let mut scheculer_job = ArticleReadTimeSchedulerJob::new(16); + let log_layer = LogLayer { target: "my_target" }; let router = Router::new() .route("/", get(articles_view)) //只返回试图 @@ -61,8 +64,7 @@ async fn main() { .with_state(pool) .nest_service("/assets", tower_http::services::ServeDir::new("assets")); - //开启scheudler - ArticleReadTimeSchedulerJob::new(16).start(); + scheculer_job.start(); // run our app with hyper, listening globally on port 3000 let listener = tokio::net::TcpListener::bind("0.0.0.0:80").await.unwrap(); diff --git a/src/scheduler_job/article_read_time_scheduler_job.rs b/src/scheduler_job/article_read_time_scheduler_job.rs index a0e73bf..1bffa43 100644 --- a/src/scheduler_job/article_read_time_scheduler_job.rs +++ b/src/scheduler_job/article_read_time_scheduler_job.rs @@ -29,8 +29,9 @@ impl ArticleReadTimeSchedulerJob { } } - pub fn get_count_vec(&self) { - + /// 返回可变引用 + pub fn get_mut_ref_count_vec(&mut self) -> &Arc>>{ + &self.count_vec } pub fn start(&self){ -- Gitee From e254f0831a85573eea1e67abbf5a6fb11e7706aa Mon Sep 17 00:00:00 2001 From: owen Date: Sat, 7 Dec 2024 22:07:37 +0800 Subject: [PATCH 05/14] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=AE=9A=E6=97=B6?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E4=B8=AD=E7=9A=84=E5=8E=9F=E5=AD=90=E7=B1=BB?= =?UTF-8?q?=EF=BC=8C=E7=A7=BB=E5=8A=A8=E6=96=87=E6=A1=A3=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 +----- ...13\345\255\220\345\255\246Rust-RcyuArc.md" | 18 ------------- src/main.rs | 25 +++++++++---------- .../article_read_time_scheduler_job.rs | 25 ++++++++----------- 4 files changed, 23 insertions(+), 52 deletions(-) delete mode 100644 "src/doc/\351\200\232\350\277\207\344\276\213\345\255\220\345\255\246Rust-RcyuArc.md" diff --git a/README.md b/README.md index ed4f3e0..4438a0d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ ### 介绍 - 使用axum 试着创建一个web app, 通过项目学习Rust, tokio, 这是个练习的项目。 ### 设计思想 @@ -13,7 +12,7 @@ 运行时:tokio 数据库:sqlx 日志:tklog -定时任务:cron +定时任务:job_scheduler 模板渲染:aksama, askama-axum 页面:html,css,js, JQuery, Bootstrap @@ -39,10 +38,6 @@ cargo build --release 时并不会帮我们把我们需要的静态文件打包 - 我们还需要把asserts文件(包含此文件夹)一同上传到执行文件的同级目录下, asserts文件夹是程序中ServeDir::new指定了该名称。 - - ### 注意 - - - 文件名建议中文,假如是英文的话,如果路径中使用反斜杠那么名字可能被转移,例如\test.md, \t 就被当作换行符。 - 路径名为全路径名称, 要改 diff --git "a/src/doc/\351\200\232\350\277\207\344\276\213\345\255\220\345\255\246Rust-RcyuArc.md" "b/src/doc/\351\200\232\350\277\207\344\276\213\345\255\220\345\255\246Rust-RcyuArc.md" deleted file mode 100644 index ac74a9a..0000000 --- "a/src/doc/\351\200\232\350\277\207\344\276\213\345\255\220\345\255\246Rust-RcyuArc.md" +++ /dev/null @@ -1,18 +0,0 @@ -### Rc与Arc -#### 目的 -Rust 中的所有权机制要求一个值一个时刻只能有一个所有者。那么在这样的情形下,就会有问题: -- 图数据库中,一个节点可以有多个边指向它; -- 多线程中每个线程可能需要修改内存中同一个值; -- 同一个线程在程序的不同部分都需要修改一个值,并且无法确定哪个修改部分先执行。 -所有权机制在这样的情况下会让程序走不下去。 -Rc/Arc 就是用来解决这样的问题的, 通过引用计数的方式让同一个值可以有多个拥有者。Rc 全城Reference Counting。 Rc 适用于单线程;Arc适用于多线程。 - -通过追踪引用数量来决定这个值是否还在使用。如果这个值有0个引用,那么它将被清理。 - -#### Rc - - -#### Reference -《The Rust Programming Language》(Rc, the Reference Counted Smart Pointer)[https://doc.rust-lang.org/book/ch15-04-rc.html] -《Rust语言圣经》(Rc与Arc实现1vN所有权机制)[https://course.rs/advance/smart-pointer/rc-arc.html] - diff --git a/src/main.rs b/src/main.rs index f3c0adf..0023f14 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,11 @@ use axum::http::HeaderValue; -use axum::{extract::Path, extract::State, routing::get, Router, Json}; +use axum::{extract::Path, extract::State, routing::get, Json, Router}; use axum_hello::entity::article_list_view::ArticlesListView; use axum_hello::layer::log_layer::LogLayer; use axum_hello::scheduler_job::article_read_time_scheduler_job::ArticleReadTimeSchedulerJob; -use tower_http::cors::{CorsLayer}; use sqlx::mysql::MySqlPoolOptions; use sqlx::MySqlPool; +use tower_http::cors::CorsLayer; use tklog::info; @@ -44,30 +44,29 @@ async fn main() { .await .expect("failed to connect database."); - //开启scheudler - let mut scheculer_job = ArticleReadTimeSchedulerJob::new(16); - - let log_layer = LogLayer { target: "my_target" }; + let log_layer = LogLayer { + target: "my_target", + }; let router = Router::new() - .route("/", get(articles_view)) //只返回试图 - .route("/articles",get(articles_view)) //只返回试图 + .route("/", get(articles_view)) //只返回试图 + .route("/articles", get(articles_view)) //只返回试图 .route("/articles/:page_num", get(article_list)) //返回具体信息 .route("/article/:id", get(article_detail)) .route("/me", get(me)) .route("/article/new", get(extract_article)) .layer( //设置同源策略 - CorsLayer::new() - .allow_origin("*".parse::().unwrap()) + CorsLayer::new().allow_origin("*".parse::().unwrap()), ) .layer(log_layer) .with_state(pool) .nest_service("/assets", tower_http::services::ServeDir::new("assets")); - scheculer_job.start(); + //开启scheudler + let _ = ArticleReadTimeSchedulerJob::new(16).start(); // run our app with hyper, listening globally on port 3000 - let listener = tokio::net::TcpListener::bind("0.0.0.0:80").await.unwrap(); + let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); axum::serve(listener, router).await.unwrap(); } @@ -81,7 +80,7 @@ async fn say_hello(State(pool): State) -> String { row.0 } -async fn articles_view() -> ArticlesListView{ +async fn articles_view() -> ArticlesListView { ArticlesListView::new() } async fn article_list( diff --git a/src/scheduler_job/article_read_time_scheduler_job.rs b/src/scheduler_job/article_read_time_scheduler_job.rs index 1bffa43..dbf4f3e 100644 --- a/src/scheduler_job/article_read_time_scheduler_job.rs +++ b/src/scheduler_job/article_read_time_scheduler_job.rs @@ -1,27 +1,27 @@ +use std::sync::atomic::AtomicU16; /// 计算文章阅读此书的定时任务 /// AtomicI8 原子类型 /// create: job_scheduler /// 每小时一次 -/// -use std::sync::{Arc, RwLock}; +/// +use std::sync::Arc; use std::thread; use std::time::Duration; use job_scheduler::{Job, JobScheduler}; pub struct ArticleReadTimeSchedulerJob { - count_vec: Arc>>, // 使用 Arc 允许多个所有权 + count_vec: Arc>, // 使用 Arc 允许多个所有权 } impl ArticleReadTimeSchedulerJob { - /// 初始化一个新的 ArticleReadTimeSchedulerJob 实例。 /// /// 参数: /// - default_count_num: 文章数量,默认是 page_size * 2。 pub fn new(default_count_num: usize) -> Self { let count_vec = (0..default_count_num) - .map(|_| RwLock::new(0)) + .map(|_| AtomicU16::new(0)) .collect::>(); ArticleReadTimeSchedulerJob { @@ -30,41 +30,36 @@ impl ArticleReadTimeSchedulerJob { } /// 返回可变引用 - pub fn get_mut_ref_count_vec(&mut self) -> &Arc>>{ + pub fn get_mut_ref_count_vec(&mut self) -> &Arc> { &self.count_vec } - pub fn start(&self){ + pub fn start(&self) { let count_vec = Arc::clone(&self.count_vec); let _ = thread::spawn(move || { let mut sched = JobScheduler::new(); - + sched.add(Job::new("1/10 * * * * *".parse().unwrap(), || { println!("hello job scheduler."); for i in count_vec.iter() { - println!("{}",i.read().unwrap()); + println!("{}", i.load(std::sync::atomic::Ordering::SeqCst)); } })); loop { sched.tick(); std::thread::sleep(Duration::from_millis(500)); } - }); } } - - #[cfg(test)] mod test { use super::*; #[test] - pub fn test_article_read_time_secheduler(){ + pub fn test_article_read_time_secheduler() { let job = ArticleReadTimeSchedulerJob::new(16); job.start(); } - } - -- Gitee From 0b3e3ba509af7751acd2e72a32b3de6c3be8e972 Mon Sep 17 00:00:00 2001 From: "owen.chao" Date: Mon, 9 Dec 2024 17:49:48 +0800 Subject: [PATCH 06/14] =?UTF-8?q?scheduelr=20=E6=94=B9=E9=80=A0=EF=BC=8Cla?= =?UTF-8?q?yer=20=E4=BF=AE=E6=94=B9=EF=BC=8C=E5=85=A8=E5=B1=80vec,?= =?UTF-8?q?=E7=BA=BF=E7=A8=8B=E5=AE=89=E5=85=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layer/mod.rs | 3 +- src/layer/{url_layper.rs => url_layer.rs} | 37 +++++++++++++++---- src/main.rs | 22 ++++++++--- .../article_read_time_scheduler_job.rs | 26 ++++++------- 4 files changed, 59 insertions(+), 29 deletions(-) rename src/layer/{url_layper.rs => url_layer.rs} (52%) diff --git a/src/layer/mod.rs b/src/layer/mod.rs index f954043..7bd7fa2 100644 --- a/src/layer/mod.rs +++ b/src/layer/mod.rs @@ -1 +1,2 @@ -pub mod log_layer; \ No newline at end of file +pub mod log_layer; +pub mod url_layer; \ No newline at end of file diff --git a/src/layer/url_layper.rs b/src/layer/url_layer.rs similarity index 52% rename from src/layer/url_layper.rs rename to src/layer/url_layer.rs index fffe521..1f67dc9 100644 --- a/src/layer/url_layper.rs +++ b/src/layer/url_layer.rs @@ -1,4 +1,7 @@ use std::{fmt, task::{Context, Poll}}; +use std::sync::{Arc, RwLock}; +use std::sync::atomic::AtomicU16; +use std::sync::atomic::Ordering; /// a demo https://tower-rs.github.io/tower/tower_layer/trait.Layer.html use tower_layer::Layer; @@ -6,16 +9,24 @@ use tower_service::Service; #[derive(Debug, Clone)] pub struct UrlLayer { - pub target: &'static str, + articls_click_vue: Arc>>, +} + +impl UrlLayer { + pub fn new(articls_click_vue:Arc>>) -> Self{ + UrlLayer{ + articls_click_vue + } + } } impl Layer for UrlLayer { - type Service = LogService; + type Service = UrlService; fn layer(&self, service: S) -> Self::Service { - LogService { - target: self.target, - service + UrlService { + service, + articls_click_vue: Arc::clone(&self.articls_click_vue), } } } @@ -23,8 +34,8 @@ impl Layer for UrlLayer { // This service implements the Log behavior #[derive(Debug, Clone)] pub struct UrlService { - target: &'static str, service: S, + articls_click_vue: Arc>>, } impl Service for UrlService @@ -42,8 +53,18 @@ where fn call(&mut self, request: Request) -> Self::Future { //更新文章访问次数值 - - + + let vec = &self.articls_click_vue.read().unwrap(); + let click_times = &vec[0]; + click_times.fetch_add(1, Ordering::SeqCst); + println!("-------------------------------------------{:?}",click_times.load(Ordering::SeqCst)); + + //vec 添加一个元素 + /* + let vec1 = &mut self.articls_click_vue.write().unwrap(); + vec1.push(10.into()); + */ + self.service.call(request) } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 0023f14..881bbdf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,11 @@ +use std::sync::{Arc, RwLock}; +use std::sync::atomic::AtomicU16; + use axum::http::HeaderValue; use axum::{extract::Path, extract::State, routing::get, Json, Router}; use axum_hello::entity::article_list_view::ArticlesListView; use axum_hello::layer::log_layer::LogLayer; +use axum_hello::layer::url_layer::UrlLayer; use axum_hello::scheduler_job::article_read_time_scheduler_job::ArticleReadTimeSchedulerJob; use sqlx::mysql::MySqlPoolOptions; use sqlx::MySqlPool; @@ -43,10 +47,17 @@ async fn main() { .connect("mysql://wukong:wk(2024)@47.92.236.188/Blog") .await .expect("failed to connect database."); - - let log_layer = LogLayer { - target: "my_target", - }; + + // default page_size=9 + let default_vec_size = 9*2; + let mut count_vec = (0..default_vec_size) + .map(|_| AtomicU16::new(0)) + .collect::>(); + let mut articles_click_vec: Arc>> = Arc::new(RwLock::new(count_vec)); + let UrlLayer = UrlLayer::new(Arc::clone(&articles_click_vec)); + let log_layer = LogLayer{ + target: "hello layer" + }; let router = Router::new() .route("/", get(articles_view)) //只返回试图 .route("/articles", get(articles_view)) //只返回试图 @@ -59,11 +70,12 @@ async fn main() { CorsLayer::new().allow_origin("*".parse::().unwrap()), ) .layer(log_layer) + .layer(UrlLayer) .with_state(pool) .nest_service("/assets", tower_http::services::ServeDir::new("assets")); //开启scheudler - let _ = ArticleReadTimeSchedulerJob::new(16).start(); + let _ = ArticleReadTimeSchedulerJob::new(Arc::clone(&articles_click_vec)).start(); // run our app with hyper, listening globally on port 3000 let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); diff --git a/src/scheduler_job/article_read_time_scheduler_job.rs b/src/scheduler_job/article_read_time_scheduler_job.rs index dbf4f3e..673ebb7 100644 --- a/src/scheduler_job/article_read_time_scheduler_job.rs +++ b/src/scheduler_job/article_read_time_scheduler_job.rs @@ -1,17 +1,19 @@ +use std::{fmt, task::{Context, Poll}}; +use std::sync::{Arc, RwLock}; use std::sync::atomic::AtomicU16; +use std::sync::atomic::Ordering; /// 计算文章阅读此书的定时任务 /// AtomicI8 原子类型 /// create: job_scheduler /// 每小时一次 -/// -use std::sync::Arc; + use std::thread; use std::time::Duration; use job_scheduler::{Job, JobScheduler}; pub struct ArticleReadTimeSchedulerJob { - count_vec: Arc>, // 使用 Arc 允许多个所有权 + articls_click_vue: Arc>>, // 使用 Arc 允许多个所有权 } impl ArticleReadTimeSchedulerJob { @@ -19,32 +21,26 @@ impl ArticleReadTimeSchedulerJob { /// /// 参数: /// - default_count_num: 文章数量,默认是 page_size * 2。 - pub fn new(default_count_num: usize) -> Self { - let count_vec = (0..default_count_num) - .map(|_| AtomicU16::new(0)) - .collect::>(); - + pub fn new(articls_click_vue: Arc>>) -> Self { ArticleReadTimeSchedulerJob { - count_vec: Arc::new(count_vec), + articls_click_vue, } } - /// 返回可变引用 - pub fn get_mut_ref_count_vec(&mut self) -> &Arc> { - &self.count_vec - } pub fn start(&self) { - let count_vec = Arc::clone(&self.count_vec); + let articls_click_vue = Arc::clone(&self.articls_click_vue); let _ = thread::spawn(move || { let mut sched = JobScheduler::new(); sched.add(Job::new("1/10 * * * * *".parse().unwrap(), || { println!("hello job scheduler."); - for i in count_vec.iter() { + for i in articls_click_vue.read().unwrap().iter() { println!("{}", i.load(std::sync::atomic::Ordering::SeqCst)); } })); + //清空重置articls_click_vue + //todo loop { sched.tick(); std::thread::sleep(Duration::from_millis(500)); -- Gitee From 3f24e900b7968edf4f063d4ded2288570e0722e8 Mon Sep 17 00:00:00 2001 From: "owen.chao" Date: Mon, 9 Dec 2024 18:40:00 +0800 Subject: [PATCH 07/14] =?UTF-8?q?=E4=BF=AE=E6=94=B9ddl,=E5=AE=9A=E6=97=B6?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=EF=BC=8Clayer=20=E6=B7=BB=E5=8A=A0=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E8=BF=9E=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/scripts/DDL.sql | 12 ++++++------ src/layer/url_layer.rs | 8 ++++++-- src/main.rs | 9 +++++---- src/scheduler_job/article_read_time_scheduler_job.rs | 6 +++++- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/resource/scripts/DDL.sql b/resource/scripts/DDL.sql index 9c6bf80..cc22e7c 100644 --- a/resource/scripts/DDL.sql +++ b/resource/scripts/DDL.sql @@ -29,14 +29,14 @@ CREATE TABLE category( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- article read count table -CREATE TABLE access_count { +CREATE TABLE access_count ( `url` VARCHAR(64) NULL, `article_id` INT(11) NULL, - `total_times` INT(11) NOT DEFAULT 0 - `create_on` DATETIME NOT NULL , + `total_times` INT(11) NOT NULL DEFAULT 0, + `create_on` DATETIME NOT NULL, `create_by` VARCHAR(32) NOT NULL, - `modify_on` DATETIME NOT NULL , - `modify_by` VARCHAR(32) NOT NULL, -} ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + `modify_on` DATETIME NOT NULL, + `modify_by` VARCHAR(32) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/src/layer/url_layer.rs b/src/layer/url_layer.rs index 1f67dc9..b4e92ab 100644 --- a/src/layer/url_layer.rs +++ b/src/layer/url_layer.rs @@ -3,6 +3,8 @@ use std::sync::{Arc, RwLock}; use std::sync::atomic::AtomicU16; use std::sync::atomic::Ordering; +use sqlx::MySqlPool; + /// a demo https://tower-rs.github.io/tower/tower_layer/trait.Layer.html use tower_layer::Layer; use tower_service::Service; @@ -10,12 +12,14 @@ use tower_service::Service; #[derive(Debug, Clone)] pub struct UrlLayer { articls_click_vue: Arc>>, + db_pool: MySqlPool, } impl UrlLayer { - pub fn new(articls_click_vue:Arc>>) -> Self{ + pub fn new(articls_click_vue:Arc>>, db_pool:MySqlPool) -> Self{ UrlLayer{ - articls_click_vue + articls_click_vue, + db_pool, } } } diff --git a/src/main.rs b/src/main.rs index 881bbdf..299caf3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,9 +54,10 @@ async fn main() { .map(|_| AtomicU16::new(0)) .collect::>(); let mut articles_click_vec: Arc>> = Arc::new(RwLock::new(count_vec)); - let UrlLayer = UrlLayer::new(Arc::clone(&articles_click_vec)); + let UrlLayer = UrlLayer::new(Arc::clone(&articles_click_vec),pool.clone()); let log_layer = LogLayer{ - target: "hello layer" + target: "hello layer", + }; let router = Router::new() .route("/", get(articles_view)) //只返回试图 @@ -71,11 +72,11 @@ async fn main() { ) .layer(log_layer) .layer(UrlLayer) - .with_state(pool) + .with_state(pool.clone()) .nest_service("/assets", tower_http::services::ServeDir::new("assets")); //开启scheudler - let _ = ArticleReadTimeSchedulerJob::new(Arc::clone(&articles_click_vec)).start(); + let _ = ArticleReadTimeSchedulerJob::new(Arc::clone(&articles_click_vec), pool.clone()).start(); // run our app with hyper, listening globally on port 3000 let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); diff --git a/src/scheduler_job/article_read_time_scheduler_job.rs b/src/scheduler_job/article_read_time_scheduler_job.rs index 673ebb7..ae7963c 100644 --- a/src/scheduler_job/article_read_time_scheduler_job.rs +++ b/src/scheduler_job/article_read_time_scheduler_job.rs @@ -10,10 +10,13 @@ use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; +use sqlx::MySqlPool; + use job_scheduler::{Job, JobScheduler}; pub struct ArticleReadTimeSchedulerJob { articls_click_vue: Arc>>, // 使用 Arc 允许多个所有权 + db_pool: MySqlPool, } impl ArticleReadTimeSchedulerJob { @@ -21,9 +24,10 @@ impl ArticleReadTimeSchedulerJob { /// /// 参数: /// - default_count_num: 文章数量,默认是 page_size * 2。 - pub fn new(articls_click_vue: Arc>>) -> Self { + pub fn new(articls_click_vue: Arc>>, db_pool: MySqlPool) -> Self { ArticleReadTimeSchedulerJob { articls_click_vue, + db_pool, } } -- Gitee From 4c510e5f3e1a9c6a27a3f583398857ece56366ab Mon Sep 17 00:00:00 2001 From: "owen.chao" Date: Mon, 9 Dec 2024 18:56:20 +0800 Subject: [PATCH 08/14] add access_count entity --- src/entity/access_count.rs | 31 +++++++++++++++++++++++++++++++ src/entity/mod.rs | 2 ++ 2 files changed, 33 insertions(+) create mode 100644 src/entity/access_count.rs diff --git a/src/entity/access_count.rs b/src/entity/access_count.rs new file mode 100644 index 0000000..6a026e1 --- /dev/null +++ b/src/entity/access_count.rs @@ -0,0 +1,31 @@ +use std::io::{ Error, ErrorKind}; + +#[derive(Clone)] +#[derive(Debug)] +pub struct AccessCount { + pub url String, + pub article_id: i32, + pub total_times: i32, +} + +impl AccessCount { + pub fn new(url:&str, article_id:i32, total_times: i32)->Self { + AccessCount{ + url, + article_id, + total_times, + } + } + + pub fn update(&self, access_count: &AccessCount) ->Result<(), Error> { + let _ = sqlx::query( + r#"INSERT INTO access_count (url, article_id, total_times, created_by, create_on, modify_by, modify_on) + VALUES (?, ?, ?,'Admin', now(), 'Admin',now() )"#, + ) + .bind(&access_count.url) + .bind(&access_count.article_id) + .bind(&access_count.total_times) + .execute(pool).await + .expect("update access_count failed"); + } +} \ No newline at end of file diff --git a/src/entity/mod.rs b/src/entity/mod.rs index d12d61f..c69db67 100644 --- a/src/entity/mod.rs +++ b/src/entity/mod.rs @@ -3,3 +3,5 @@ pub mod article_detail; pub mod article_summary; pub mod article_summary_vec; pub mod article_list_view; +pub mod access_count; + -- Gitee From ef8bc8a736ed05665ae8843b258084d3e544d75f Mon Sep 17 00:00:00 2001 From: "owen.chao" Date: Mon, 9 Dec 2024 18:57:10 +0800 Subject: [PATCH 09/14] add access_count entity --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 299caf3..2a78a6d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,7 +44,7 @@ async fn main() { let basic_path = Arc::new(args[1].clone()); */ let pool = MySqlPoolOptions::new() - .connect("mysql://wukong:wk(2024)@47.92.236.188/Blog") + .connect("mysql://account:password@address/Blog") .await .expect("failed to connect database."); -- Gitee From 4aaaf562a748648366f342e6bc3fce4de94aaa90 Mon Sep 17 00:00:00 2001 From: "owen.chao" Date: Wed, 11 Dec 2024 16:19:12 +0800 Subject: [PATCH 10/14] modify the layer and add the logic to get the uri --- src/entity/mod.rs | 2 +- src/layer/url_layer.rs | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/entity/mod.rs b/src/entity/mod.rs index c69db67..96f6183 100644 --- a/src/entity/mod.rs +++ b/src/entity/mod.rs @@ -3,5 +3,5 @@ pub mod article_detail; pub mod article_summary; pub mod article_summary_vec; pub mod article_list_view; -pub mod access_count; +//pub mod access_count; diff --git a/src/layer/url_layer.rs b/src/layer/url_layer.rs index b4e92ab..a3e2078 100644 --- a/src/layer/url_layer.rs +++ b/src/layer/url_layer.rs @@ -3,6 +3,10 @@ use std::sync::{Arc, RwLock}; use std::sync::atomic::AtomicU16; use std::sync::atomic::Ordering; +use axum::body::Body; +use axum::http::Request; +use axum::http::Uri; + use sqlx::MySqlPool; /// a demo https://tower-rs.github.io/tower/tower_layer/trait.Layer.html @@ -42,10 +46,9 @@ pub struct UrlService { articls_click_vue: Arc>>, } -impl Service for UrlService +impl Service> for UrlService where - S: Service, - Request: fmt::Debug, + S: Service> { type Response = S::Response; type Error = S::Error; @@ -55,8 +58,12 @@ where self.service.poll_ready(cx) } - fn call(&mut self, request: Request) -> Self::Future { + fn call(&mut self, request: Request) -> Self::Future { //更新文章访问次数值 + let uri = request.uri(); + println!("the path is : {}",uri); + + let vec = &self.articls_click_vue.read().unwrap(); let click_times = &vec[0]; -- Gitee From f40bf8d0dc9ffac0d8b5cb33f84b3405867ee605 Mon Sep 17 00:00:00 2001 From: "owen.chao" Date: Fri, 13 Dec 2024 15:27:21 +0800 Subject: [PATCH 11/14] uri-layer --- src/layer/url_layer.rs | 4 +--- src/main.rs | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/layer/url_layer.rs b/src/layer/url_layer.rs index a3e2078..6a1c1be 100644 --- a/src/layer/url_layer.rs +++ b/src/layer/url_layer.rs @@ -16,14 +16,12 @@ use tower_service::Service; #[derive(Debug, Clone)] pub struct UrlLayer { articls_click_vue: Arc>>, - db_pool: MySqlPool, } impl UrlLayer { - pub fn new(articls_click_vue:Arc>>, db_pool:MySqlPool) -> Self{ + pub fn new(articls_click_vue:Arc>>) -> Self{ UrlLayer{ articls_click_vue, - db_pool, } } } diff --git a/src/main.rs b/src/main.rs index 2a78a6d..8356b4a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,7 +44,7 @@ async fn main() { let basic_path = Arc::new(args[1].clone()); */ let pool = MySqlPoolOptions::new() - .connect("mysql://account:password@address/Blog") + .connect("mysql://user:pass@address/Blog") .await .expect("failed to connect database."); @@ -54,7 +54,7 @@ async fn main() { .map(|_| AtomicU16::new(0)) .collect::>(); let mut articles_click_vec: Arc>> = Arc::new(RwLock::new(count_vec)); - let UrlLayer = UrlLayer::new(Arc::clone(&articles_click_vec),pool.clone()); + let UrlLayer = UrlLayer::new(Arc::clone(&articles_click_vec)); let log_layer = LogLayer{ target: "hello layer", @@ -72,7 +72,7 @@ async fn main() { ) .layer(log_layer) .layer(UrlLayer) - .with_state(pool.clone()) + .with_state(pool) .nest_service("/assets", tower_http::services::ServeDir::new("assets")); //开启scheudler -- Gitee From 610792c9124f69dd18ebc9ce4311371b6972f344 Mon Sep 17 00:00:00 2001 From: owen Date: Sat, 14 Dec 2024 22:41:42 +0800 Subject: [PATCH 12/14] add article click count --- Cargo.toml | 3 +- assets/js/index-vue.js | 2 +- src/layer/url_layer.rs | 98 ++++++++++++------- src/main.rs | 29 +++--- .../article_read_time_scheduler_job.rs | 22 ++--- 5 files changed, 90 insertions(+), 64 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cf1f93c..273c6fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ chrono = "0.4.38" cron = "0.12.1" tklog = "0.0.10" job_scheduler = "1.2.1" +regex = "1.11.1" serde = {version="1.0.214", features = ["derive"]} -serde_json = "1.0.132" \ No newline at end of file +serde_json = "1.0.132" diff --git a/assets/js/index-vue.js b/assets/js/index-vue.js index b116ab1..03656e1 100644 --- a/assets/js/index-vue.js +++ b/assets/js/index-vue.js @@ -23,4 +23,4 @@ * @vue/runtime-dom v3.5.13 * (c) 2018-present Yuxi (Evan) You and Vue contributors * @license MIT -**/let Zr;const Ra=typeof window<"u"&&window.trustedTypes;if(Ra)try{Zr=Ra.createPolicy("vue",{createHTML:e=>e})}catch{}const gu=Zr?e=>Zr.createHTML(e):e=>e,Ug="http://www.w3.org/2000/svg",Wg="http://www.w3.org/1998/Math/MathML",Vt=typeof document<"u"?document:null,La=Vt&&Vt.createElement("template"),qg={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,i)=>{const r=t==="svg"?Vt.createElementNS(Ug,e):t==="mathml"?Vt.createElementNS(Wg,e):n?Vt.createElement(e,{is:n}):Vt.createElement(e);return e==="select"&&i&&i.multiple!=null&&r.setAttribute("multiple",i.multiple),r},createText:e=>Vt.createTextNode(e),createComment:e=>Vt.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Vt.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,i,r,l){const a=n?n.previousSibling:t.lastChild;if(r&&(r===l||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),n),!(r===l||!(r=r.nextSibling)););else{La.innerHTML=gu(i==="svg"?`${e}`:i==="mathml"?`${e}`:e);const u=La.content;if(i==="svg"||i==="mathml"){const d=u.firstChild;for(;d.firstChild;)u.appendChild(d.firstChild);u.removeChild(d)}t.insertBefore(u,n)}return[a?a.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},Vg=Symbol("_vtc");function zg(e,t,n){const i=e[Vg];i&&(t=(t?[t,...i]:[...i]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const Da=Symbol("_vod"),Kg=Symbol("_vsh"),Jg=Symbol(""),Xg=/(^|;)\s*display\s*:/;function Yg(e,t,n){const i=e.style,r=Pe(n);let l=!1;if(n&&!r){if(t)if(Pe(t))for(const a of t.split(";")){const u=a.slice(0,a.indexOf(":")).trim();n[u]==null&&gi(i,u,"")}else for(const a in t)n[a]==null&&gi(i,a,"");for(const a in n)a==="display"&&(l=!0),gi(i,a,n[a])}else if(r){if(t!==n){const a=i[Jg];a&&(n+=";"+a),i.cssText=n,l=Xg.test(n)}}else t&&e.removeAttribute("style");Da in e&&(e[Da]=l?i.display:"",e[Kg]&&(i.display="none"))}const Na=/\s*!important$/;function gi(e,t,n){if(ee(n))n.forEach(i=>gi(e,t,i));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const i=Gg(e,t);Na.test(n)?e.setProperty(kn(i),n.replace(Na,""),"important"):e[i]=n}}const Ia=["Webkit","Moz","ms"],Nr={};function Gg(e,t){const n=Nr[t];if(n)return n;let i=rn(t);if(i!=="filter"&&i in e)return Nr[t]=i;i=mc(i);for(let r=0;rIr||(nm.then(()=>Ir=0),Ir=Date.now());function im(e,t){const n=i=>{if(!i._vts)i._vts=Date.now();else if(i._vts<=n.attached)return;$t(rm(i,n.value),t,5,[i])};return n.value=e,n.attached=sm(),n}function rm(e,t){if(ee(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(i=>r=>!r._stopped&&i&&i(r))}else return t}const Ba=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,om=(e,t,n,i,r,l)=>{const a=r==="svg";t==="class"?zg(e,i,a):t==="style"?Yg(e,n,i):Ri(t)?ro(t)||em(e,t,n,i,l):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):lm(e,t,i,a))?(Fa(e,t,i),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&ka(e,t,i,a,l,t!=="value")):e._isVueCE&&(/[A-Z]/.test(t)||!Pe(i))?Fa(e,rn(t),i,l,t):(t==="true-value"?e._trueValue=i:t==="false-value"&&(e._falseValue=i),ka(e,t,i,a))};function lm(e,t,n,i){if(i)return!!(t==="innerHTML"||t==="textContent"||t in e&&Ba(t)&&re(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const r=e.tagName;if(r==="IMG"||r==="VIDEO"||r==="CANVAS"||r==="SOURCE")return!1}return Ba(t)&&Pe(n)?!1:t in e}const am=je({patchProp:om},qg);let Ha;function cm(){return Ha||(Ha=pg(am))}const um=(...e)=>{const t=cm().createApp(...e),{mount:n}=t;return t.mount=i=>{const r=dm(i);if(!r)return;const l=t._component;!re(l)&&!l.render&&!l.template&&(l.template=r.innerHTML),r.nodeType===1&&(r.textContent="");const a=n(r,!1,fm(r));return r instanceof Element&&(r.removeAttribute("v-cloak"),r.setAttribute("data-v-app","")),a},t};function fm(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function dm(e){return Pe(e)?document.querySelector(e):e}const Ao=(e,t)=>{const n=e.__vccOpts||e;for(const[i,r]of t)n[i]=r;return n},hm={class:"card shadow white black_blog text-break blog move-trans-toLeft",style:{"min-height":"200px","margin-bottom":"15pt"}},pm=["href"],gm={class:"card-title"},mm={class:"tags"},_m={class:"tag"},bm={class:"card-text"},ym={__name:"Blog",props:{blog:{id:Number,title:String,author:String,summary:String,tag:void 0,createdOn:Date,viewAmount:0}},setup(e){return(t,n)=>(Me(),$e("div",hm,[et("a",{class:"card-body",href:`/article/${e.blog.id}`,style:{"text-decoration":"none",display:"block"}},[et("h4",gm,Ln(e.blog.title),1),et("div",mm,[(Me(!0),$e(Ze,null,As(e.blog.tag,i=>(Me(),$e("span",_m,Ln(i),1))),256))]),et("p",bm,Ln(e.blog.summary),1)],8,pm)]))}},Mr=Ao(ym,[["__scopeId","data-v-bfbe1513"]]),vm={class:"btn-group",role:"group","aria-label":"Basic example"},wm=["onClick"],kr=10,Em={__name:"Pagination",props:{total:{type:Number,required:!0},currentPage:{type:Number,default:1},pageSize:{type:Number,default:9},prevText:{type:String,default:"上一页"},nextText:{type:String,default:"下一页"}},emits:["blogsUpdate"],setup(e,{emit:t}){const n=rs("axios"),i=t,r=e,l=Zt([]),a=Zt(r.currentPage),u=pi(()=>Math.ceil(r.total/r.pageSize)),d=pi(()=>Math.floor((a.value-1)/kr)),g=pi(()=>{const C=d.value*kr+1,$=Math.min(C+kr-1,u.value);return Array.from({length:$-C+1},(q,Y)=>C+Y)});Ss(()=>r.currentPage,C=>{a.value=C},{immediate:!0}),vo(()=>{console.log("Pagination>>>>>>>>>>>>>>>>>>>>>"+r.total)});function h(){a.value!==1&&(a.value=1,N(a.value))}function y(){a.value>1&&(a.value--,N(a.value))}function T(){a.value=1&&C<=u.value&&(a.value=C,N(C))}function N(C){n.get(`articles/${C}`).then($=>{console.log("---------------------------"),console.log($),l.value=[],l.value.push(...$.data.vec1||[]),l.value.push(...$.data.vec2||[]),l.value.push(...$.data.vec3||[]),i("blogsUpdate",l)}).catch($=>{console.error("Error fetching data:",$)})}return(C,$)=>(Me(),$e("div",vm,[et("button",{type:"button",class:"btn btn-light text-nowrap",onClick:h},"首页"),et("button",{type:"button",class:"btn btn-light text-nowrap",onClick:y},Ln(e.prevText),1),(Me(!0),$e(Ze,null,As(g.value,q=>(Me(),$e("button",{key:q,type:"button",class:Ii(["btn btn-light",{active:q===a.value}]),onClick:Y=>L(q)},Ln(q),11,wm))),128)),et("button",{type:"button",class:"btn btn-light text-nowrap",onClick:D},Ln(e.nextText),1),et("button",{type:"button",class:"btn btn-light text-nowrap",onClick:T},"尾页")]))}},xm=Ao(Em,[["__scopeId","data-v-c9e8b975"]]),Tm={key:0,col:"row",style:{"margin-top":"25pt"}},Am={class:"row"},Om={class:"col"},Sm={class:"col"},Cm={class:"col"},Pm={key:1,class:"container text-center mb-5 move-trans-toRight position-absolute bottom-0"},Rm={key:2,class:"text-center"},Lm={__name:"BlogList",setup(e){const t=rs("axios");let n=Zt([]),i=Zt([]),r=Zt([]),l=Zt(1),a=Zt(0),u=9,d=Zt(!0);vo(()=>{t.get("articles/1").then(h=>{console.log("---------------------------"),console.log(h),n.value=h.data.vec1||[],i.value=h.data.vec2||[],r.value=h.data.vec3||[],l.value=h.data.page_num||1,a.value=h.data.total||0,d.value=!1}).catch(h=>{console.error("Error fetching data:",h),d.value=!1})});function g(h){console.log("father component: ",h),h.length>2?[n.value,i.value,r.value]=h:h.length>1?[n.value,i.value]=h:h.length>0&&(n.value=h[0])}return Ss(a,h=>{console.log("Total updated to:",h)}),(h,y)=>(Me(),$e(Ze,null,[qt(d)?Dr("",!0):(Me(),$e("div",Tm,[et("div",Am,[et("div",Om,[(Me(!0),$e(Ze,null,As(qt(n),T=>(Me(),$e("div",{key:T.id},[ft(Mr,{blog:T},null,8,["blog"])]))),128))]),et("div",Sm,[(Me(!0),$e(Ze,null,As(qt(i),T=>(Me(),$e("div",{key:T.id},[ft(Mr,{blog:T},null,8,["blog"])]))),128))]),et("div",Cm,[(Me(!0),$e(Ze,null,As(qt(r),T=>(Me(),$e("div",{key:T.id},[ft(Mr,{blog:T},null,8,["blog"])]))),128))])])])),qt(d)?Dr("",!0):(Me(),$e("div",Pm,[ft(xm,{total:qt(a),pageSize:qt(u),onBlogsUpdate:g},null,8,["total","pageSize"])])),qt(d)?(Me(),$e("div",Rm,y[0]||(y[0]=[et("p",null,"Loading...",-1)]))):Dr("",!0)],64))}},Dm=Ao(Lm,[["__scopeId","data-v-9787e799"]]),Nm={__name:"App",setup(e){return(t,n)=>(Me(),uu(Dm))}},Im=Ce.create({baseURL:"http://www.rustu.fun/",timeout:5e3,headers:{}});um(Nm).provide("axios",Im).mount("#app"); +**/let Zr;const Ra=typeof window<"u"&&window.trustedTypes;if(Ra)try{Zr=Ra.createPolicy("vue",{createHTML:e=>e})}catch{}const gu=Zr?e=>Zr.createHTML(e):e=>e,Ug="http://www.w3.org/2000/svg",Wg="http://www.w3.org/1998/Math/MathML",Vt=typeof document<"u"?document:null,La=Vt&&Vt.createElement("template"),qg={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,i)=>{const r=t==="svg"?Vt.createElementNS(Ug,e):t==="mathml"?Vt.createElementNS(Wg,e):n?Vt.createElement(e,{is:n}):Vt.createElement(e);return e==="select"&&i&&i.multiple!=null&&r.setAttribute("multiple",i.multiple),r},createText:e=>Vt.createTextNode(e),createComment:e=>Vt.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Vt.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,i,r,l){const a=n?n.previousSibling:t.lastChild;if(r&&(r===l||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),n),!(r===l||!(r=r.nextSibling)););else{La.innerHTML=gu(i==="svg"?`${e}`:i==="mathml"?`${e}`:e);const u=La.content;if(i==="svg"||i==="mathml"){const d=u.firstChild;for(;d.firstChild;)u.appendChild(d.firstChild);u.removeChild(d)}t.insertBefore(u,n)}return[a?a.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},Vg=Symbol("_vtc");function zg(e,t,n){const i=e[Vg];i&&(t=(t?[t,...i]:[...i]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const Da=Symbol("_vod"),Kg=Symbol("_vsh"),Jg=Symbol(""),Xg=/(^|;)\s*display\s*:/;function Yg(e,t,n){const i=e.style,r=Pe(n);let l=!1;if(n&&!r){if(t)if(Pe(t))for(const a of t.split(";")){const u=a.slice(0,a.indexOf(":")).trim();n[u]==null&&gi(i,u,"")}else for(const a in t)n[a]==null&&gi(i,a,"");for(const a in n)a==="display"&&(l=!0),gi(i,a,n[a])}else if(r){if(t!==n){const a=i[Jg];a&&(n+=";"+a),i.cssText=n,l=Xg.test(n)}}else t&&e.removeAttribute("style");Da in e&&(e[Da]=l?i.display:"",e[Kg]&&(i.display="none"))}const Na=/\s*!important$/;function gi(e,t,n){if(ee(n))n.forEach(i=>gi(e,t,i));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const i=Gg(e,t);Na.test(n)?e.setProperty(kn(i),n.replace(Na,""),"important"):e[i]=n}}const Ia=["Webkit","Moz","ms"],Nr={};function Gg(e,t){const n=Nr[t];if(n)return n;let i=rn(t);if(i!=="filter"&&i in e)return Nr[t]=i;i=mc(i);for(let r=0;rIr||(nm.then(()=>Ir=0),Ir=Date.now());function im(e,t){const n=i=>{if(!i._vts)i._vts=Date.now();else if(i._vts<=n.attached)return;$t(rm(i,n.value),t,5,[i])};return n.value=e,n.attached=sm(),n}function rm(e,t){if(ee(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(i=>r=>!r._stopped&&i&&i(r))}else return t}const Ba=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,om=(e,t,n,i,r,l)=>{const a=r==="svg";t==="class"?zg(e,i,a):t==="style"?Yg(e,n,i):Ri(t)?ro(t)||em(e,t,n,i,l):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):lm(e,t,i,a))?(Fa(e,t,i),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&ka(e,t,i,a,l,t!=="value")):e._isVueCE&&(/[A-Z]/.test(t)||!Pe(i))?Fa(e,rn(t),i,l,t):(t==="true-value"?e._trueValue=i:t==="false-value"&&(e._falseValue=i),ka(e,t,i,a))};function lm(e,t,n,i){if(i)return!!(t==="innerHTML"||t==="textContent"||t in e&&Ba(t)&&re(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const r=e.tagName;if(r==="IMG"||r==="VIDEO"||r==="CANVAS"||r==="SOURCE")return!1}return Ba(t)&&Pe(n)?!1:t in e}const am=je({patchProp:om},qg);let Ha;function cm(){return Ha||(Ha=pg(am))}const um=(...e)=>{const t=cm().createApp(...e),{mount:n}=t;return t.mount=i=>{const r=dm(i);if(!r)return;const l=t._component;!re(l)&&!l.render&&!l.template&&(l.template=r.innerHTML),r.nodeType===1&&(r.textContent="");const a=n(r,!1,fm(r));return r instanceof Element&&(r.removeAttribute("v-cloak"),r.setAttribute("data-v-app","")),a},t};function fm(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function dm(e){return Pe(e)?document.querySelector(e):e}const Ao=(e,t)=>{const n=e.__vccOpts||e;for(const[i,r]of t)n[i]=r;return n},hm={class:"card shadow white black_blog text-break blog move-trans-toLeft",style:{"min-height":"200px","margin-bottom":"15pt"}},pm=["href"],gm={class:"card-title"},mm={class:"tags"},_m={class:"tag"},bm={class:"card-text"},ym={__name:"Blog",props:{blog:{id:Number,title:String,author:String,summary:String,tag:void 0,createdOn:Date,viewAmount:0}},setup(e){return(t,n)=>(Me(),$e("div",hm,[et("a",{class:"card-body",href:`/article/${e.blog.id}`,style:{"text-decoration":"none",display:"block"}},[et("h4",gm,Ln(e.blog.title),1),et("div",mm,[(Me(!0),$e(Ze,null,As(e.blog.tag,i=>(Me(),$e("span",_m,Ln(i),1))),256))]),et("p",bm,Ln(e.blog.summary),1)],8,pm)]))}},Mr=Ao(ym,[["__scopeId","data-v-bfbe1513"]]),vm={class:"btn-group",role:"group","aria-label":"Basic example"},wm=["onClick"],kr=10,Em={__name:"Pagination",props:{total:{type:Number,required:!0},currentPage:{type:Number,default:1},pageSize:{type:Number,default:9},prevText:{type:String,default:"上一页"},nextText:{type:String,default:"下一页"}},emits:["blogsUpdate"],setup(e,{emit:t}){const n=rs("axios"),i=t,r=e,l=Zt([]),a=Zt(r.currentPage),u=pi(()=>Math.ceil(r.total/r.pageSize)),d=pi(()=>Math.floor((a.value-1)/kr)),g=pi(()=>{const C=d.value*kr+1,$=Math.min(C+kr-1,u.value);return Array.from({length:$-C+1},(q,Y)=>C+Y)});Ss(()=>r.currentPage,C=>{a.value=C},{immediate:!0}),vo(()=>{console.log("Pagination>>>>>>>>>>>>>>>>>>>>>"+r.total)});function h(){a.value!==1&&(a.value=1,N(a.value))}function y(){a.value>1&&(a.value--,N(a.value))}function T(){a.value=1&&C<=u.value&&(a.value=C,N(C))}function N(C){n.get(`articles/${C}`).then($=>{console.log("---------------------------"),console.log($),l.value=[],l.value.push(...$.data.vec1||[]),l.value.push(...$.data.vec2||[]),l.value.push(...$.data.vec3||[]),i("blogsUpdate",l)}).catch($=>{console.error("Error fetching data:",$)})}return(C,$)=>(Me(),$e("div",vm,[et("button",{type:"button",class:"btn btn-light text-nowrap",onClick:h},"首页"),et("button",{type:"button",class:"btn btn-light text-nowrap",onClick:y},Ln(e.prevText),1),(Me(!0),$e(Ze,null,As(g.value,q=>(Me(),$e("button",{key:q,type:"button",class:Ii(["btn btn-light",{active:q===a.value}]),onClick:Y=>L(q)},Ln(q),11,wm))),128)),et("button",{type:"button",class:"btn btn-light text-nowrap",onClick:D},Ln(e.nextText),1),et("button",{type:"button",class:"btn btn-light text-nowrap",onClick:T},"尾页")]))}},xm=Ao(Em,[["__scopeId","data-v-c9e8b975"]]),Tm={key:0,col:"row",style:{"margin-top":"25pt"}},Am={class:"row"},Om={class:"col"},Sm={class:"col"},Cm={class:"col"},Pm={key:1,class:"container text-center mb-5 move-trans-toRight position-absolute bottom-0"},Rm={key:2,class:"text-center"},Lm={__name:"BlogList",setup(e){const t=rs("axios");let n=Zt([]),i=Zt([]),r=Zt([]),l=Zt(1),a=Zt(0),u=9,d=Zt(!0);vo(()=>{t.get("articles/1").then(h=>{console.log("---------------------------"),console.log(h),n.value=h.data.vec1||[],i.value=h.data.vec2||[],r.value=h.data.vec3||[],l.value=h.data.page_num||1,a.value=h.data.total||0,d.value=!1}).catch(h=>{console.error("Error fetching data:",h),d.value=!1})});function g(h){console.log("father component: ",h),h.length>2?[n.value,i.value,r.value]=h:h.length>1?[n.value,i.value]=h:h.length>0&&(n.value=h[0])}return Ss(a,h=>{console.log("Total updated to:",h)}),(h,y)=>(Me(),$e(Ze,null,[qt(d)?Dr("",!0):(Me(),$e("div",Tm,[et("div",Am,[et("div",Om,[(Me(!0),$e(Ze,null,As(qt(n),T=>(Me(),$e("div",{key:T.id},[ft(Mr,{blog:T},null,8,["blog"])]))),128))]),et("div",Sm,[(Me(!0),$e(Ze,null,As(qt(i),T=>(Me(),$e("div",{key:T.id},[ft(Mr,{blog:T},null,8,["blog"])]))),128))]),et("div",Cm,[(Me(!0),$e(Ze,null,As(qt(r),T=>(Me(),$e("div",{key:T.id},[ft(Mr,{blog:T},null,8,["blog"])]))),128))])])])),qt(d)?Dr("",!0):(Me(),$e("div",Pm,[ft(xm,{total:qt(a),pageSize:qt(u),onBlogsUpdate:g},null,8,["total","pageSize"])])),qt(d)?(Me(),$e("div",Rm,y[0]||(y[0]=[et("p",null,"Loading...",-1)]))):Dr("",!0)],64))}},Dm=Ao(Lm,[["__scopeId","data-v-9787e799"]]),Nm={__name:"App",setup(e){return(t,n)=>(Me(),uu(Dm))}},Im=Ce.create({baseURL:"http://localhost:3000/",timeout:5e3,headers:{}});um(Nm).provide("axios",Im).mount("#app"); diff --git a/src/layer/url_layer.rs b/src/layer/url_layer.rs index 6a1c1be..aa76c92 100644 --- a/src/layer/url_layer.rs +++ b/src/layer/url_layer.rs @@ -1,13 +1,14 @@ -use std::{fmt, task::{Context, Poll}}; -use std::sync::{Arc, RwLock}; -use std::sync::atomic::AtomicU16; -use std::sync::atomic::Ordering; - use axum::body::Body; use axum::http::Request; -use axum::http::Uri; - -use sqlx::MySqlPool; +use regex::Regex; +use std::sync::atomic::AtomicU16; +use std::sync::atomic::Ordering; +use std::sync::{Arc, RwLock}; +use std::{ + fmt, + task::{Context, Poll}, +}; +use tklog::warn; /// a demo https://tower-rs.github.io/tower/tower_layer/trait.Layer.html use tower_layer::Layer; @@ -15,15 +16,13 @@ use tower_service::Service; #[derive(Debug, Clone)] pub struct UrlLayer { - articls_click_vue: Arc>>, + articls_click_vue: Arc>>, } impl UrlLayer { - pub fn new(articls_click_vue:Arc>>) -> Self{ - UrlLayer{ - articls_click_vue, - } - } + pub fn new(articls_click_vue: Arc>>) -> Self { + UrlLayer { articls_click_vue } + } } impl Layer for UrlLayer { @@ -32,7 +31,7 @@ impl Layer for UrlLayer { fn layer(&self, service: S) -> Self::Service { UrlService { service, - articls_click_vue: Arc::clone(&self.articls_click_vue), + articls_click_vue: Arc::clone(&self.articls_click_vue), } } } @@ -41,12 +40,12 @@ impl Layer for UrlLayer { #[derive(Debug, Clone)] pub struct UrlService { service: S, - articls_click_vue: Arc>>, + articls_click_vue: Arc>>, } impl Service> for UrlService where - S: Service> + S: Service>, { type Response = S::Response; type Error = S::Error; @@ -57,23 +56,52 @@ where } fn call(&mut self, request: Request) -> Self::Future { - //更新文章访问次数值 - let uri = request.uri(); - println!("the path is : {}",uri); - - - - let vec = &self.articls_click_vue.read().unwrap(); - let click_times = &vec[0]; - click_times.fetch_add(1, Ordering::SeqCst); - println!("-------------------------------------------{:?}",click_times.load(Ordering::SeqCst)); - - //vec 添加一个元素 - /* - let vec1 = &mut self.articls_click_vue.write().unwrap(); - vec1.push(10.into()); - */ - + let uri = request.uri(); + println!("the path is : {}", uri); + let re = Regex::new(r"/article/(?P\d+)").unwrap(); + + if let Some(captures) = re.captures(uri.path()) { + if let Some(id_match) = captures.name("id") { + println!("Extracted ID: {}", id_match.as_str()); + let index: usize = match id_match.as_str().parse() { + Ok(num) => num, + Err(_) => { + warn!("Failed to parse article ID."); + return self.service.call(request); + } + }; + let mut vec = self.articls_click_vue.write().unwrap(); + // 确保 vec 的长度至少为 index + 1 + while vec.len() <= index { + vec.push(AtomicU16::new(0)); + } + // 更新点击次数 + vec[index].fetch_add(1, Ordering::SeqCst); + println!( + "Article {} has been clicked {} times.", + index, + vec[index].load(Ordering::SeqCst) + ); + } + } else { + println!("No match found."); + } self.service.call(request) } -} \ No newline at end of file +} + +#[cfg(test)] +mod test { + use super::*; + use regex::Regex; + + #[test] + pub fn test_regex() { + let re = Regex::new(r"Hello (?\w+)!").unwrap(); + let Some(caps) = re.captures("Hello Murphy!") else { + println!("no match!"); + return; + }; + println!("The name is: {}", &caps["name"]); + } +} diff --git a/src/main.rs b/src/main.rs index 8356b4a..f4490ac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ -use std::sync::{Arc, RwLock}; use std::sync::atomic::AtomicU16; +use std::sync::{Arc, RwLock}; use axum::http::HeaderValue; use axum::{extract::Path, extract::State, routing::get, Json, Router}; @@ -47,18 +47,17 @@ async fn main() { .connect("mysql://user:pass@address/Blog") .await .expect("failed to connect database."); - - // default page_size=9 - let default_vec_size = 9*2; - let mut count_vec = (0..default_vec_size) - .map(|_| AtomicU16::new(0)) - .collect::>(); - let mut articles_click_vec: Arc>> = Arc::new(RwLock::new(count_vec)); - let UrlLayer = UrlLayer::new(Arc::clone(&articles_click_vec)); - let log_layer = LogLayer{ - target: "hello layer", - - }; + + // default page_size=9 + let default_vec_size = 9 * 2; + let count_vec = (0..default_vec_size) + .map(|_| AtomicU16::new(0)) + .collect::>(); + let mut articles_click_vec: Arc>> = Arc::new(RwLock::new(count_vec)); + let UrlLayer = UrlLayer::new(Arc::clone(&articles_click_vec)); + let log_layer = LogLayer { + target: "hello layer", + }; let router = Router::new() .route("/", get(articles_view)) //只返回试图 .route("/articles", get(articles_view)) //只返回试图 @@ -71,8 +70,8 @@ async fn main() { CorsLayer::new().allow_origin("*".parse::().unwrap()), ) .layer(log_layer) - .layer(UrlLayer) - .with_state(pool) + .layer(UrlLayer) + .with_state(pool.clone()) .nest_service("/assets", tower_http::services::ServeDir::new("assets")); //开启scheudler diff --git a/src/scheduler_job/article_read_time_scheduler_job.rs b/src/scheduler_job/article_read_time_scheduler_job.rs index ae7963c..4be3bbc 100644 --- a/src/scheduler_job/article_read_time_scheduler_job.rs +++ b/src/scheduler_job/article_read_time_scheduler_job.rs @@ -1,14 +1,16 @@ -use std::{fmt, task::{Context, Poll}}; -use std::sync::{Arc, RwLock}; use std::sync::atomic::AtomicU16; use std::sync::atomic::Ordering; +use std::sync::{Arc, RwLock}; /// 计算文章阅读此书的定时任务 /// AtomicI8 原子类型 /// create: job_scheduler /// 每小时一次 - use std::thread; use std::time::Duration; +use std::{ + fmt, + task::{Context, Poll}, +}; use sqlx::MySqlPool; @@ -16,7 +18,7 @@ use job_scheduler::{Job, JobScheduler}; pub struct ArticleReadTimeSchedulerJob { articls_click_vue: Arc>>, // 使用 Arc 允许多个所有权 - db_pool: MySqlPool, + db_pool: MySqlPool, } impl ArticleReadTimeSchedulerJob { @@ -27,11 +29,10 @@ impl ArticleReadTimeSchedulerJob { pub fn new(articls_click_vue: Arc>>, db_pool: MySqlPool) -> Self { ArticleReadTimeSchedulerJob { articls_click_vue, - db_pool, + db_pool, } } - pub fn start(&self) { let articls_click_vue = Arc::clone(&self.articls_click_vue); let _ = thread::spawn(move || { @@ -43,8 +44,8 @@ impl ArticleReadTimeSchedulerJob { println!("{}", i.load(std::sync::atomic::Ordering::SeqCst)); } })); - //清空重置articls_click_vue - //todo + //清空重置articls_click_vue + //todo loop { sched.tick(); std::thread::sleep(Duration::from_millis(500)); @@ -58,8 +59,5 @@ mod test { use super::*; #[test] - pub fn test_article_read_time_secheduler() { - let job = ArticleReadTimeSchedulerJob::new(16); - job.start(); - } + pub fn test_article_read_time_secheduler() {} } -- Gitee From 1c3d45ebbe6b41e30f506a862cfcacbf89040d7d Mon Sep 17 00:00:00 2001 From: owen Date: Mon, 16 Dec 2024 23:17:24 +0800 Subject: [PATCH 13/14] update access time --- src/entity/access_count.rs | 39 ++++++++++++------- src/entity/mod.rs | 3 +- src/main.rs | 2 +- .../article_read_time_scheduler_job.rs | 37 +++++++++++++----- tklogsize.txt | 39 +++++++++++++++++++ 5 files changed, 92 insertions(+), 28 deletions(-) create mode 100644 tklogsize.txt diff --git a/src/entity/access_count.rs b/src/entity/access_count.rs index 6a026e1..b9dd5a8 100644 --- a/src/entity/access_count.rs +++ b/src/entity/access_count.rs @@ -1,31 +1,40 @@ use std::io::{ Error, ErrorKind}; +use sqlx::MySqlPool; #[derive(Clone)] #[derive(Debug)] -pub struct AccessCount { - pub url String, +pub struct AccessCount<'a> { + pub url :&'a str, pub article_id: i32, pub total_times: i32, } -impl AccessCount { - pub fn new(url:&str, article_id:i32, total_times: i32)->Self { +impl<'a> AccessCount<'a> { + pub fn new(url:&'a str, article_id:i32, total_times: i32)->Self { AccessCount{ url, article_id, total_times, } } - - pub fn update(&self, access_count: &AccessCount) ->Result<(), Error> { - let _ = sqlx::query( - r#"INSERT INTO access_count (url, article_id, total_times, created_by, create_on, modify_by, modify_on) - VALUES (?, ?, ?,'Admin', now(), 'Admin',now() )"#, + + pub async fn update(&self, pool :&MySqlPool) ->Result<(), Error> { + println!("execute update ---------------------{}", &self.total_times); + let result_update = sqlx::query( + r#" + UPDATE access_count + SET total_times =total_times+1 + WHERE article_id = 4"# ) - .bind(&access_count.url) - .bind(&access_count.article_id) - .bind(&access_count.total_times) - .execute(pool).await - .expect("update access_count failed"); + //.bind(&self.total_times) + //.bind(&self.article_id) + .execute(pool).await; + match result_update { + Ok(_) => {Ok(())}, + Err(e) => { + eprintln!("error .............."); + Err(Error::new(ErrorKind::Other, e.to_string())) + }, + } } -} \ No newline at end of file +} diff --git a/src/entity/mod.rs b/src/entity/mod.rs index 96f6183..66e4c9d 100644 --- a/src/entity/mod.rs +++ b/src/entity/mod.rs @@ -3,5 +3,4 @@ pub mod article_detail; pub mod article_summary; pub mod article_summary_vec; pub mod article_list_view; -//pub mod access_count; - +pub mod access_count; diff --git a/src/main.rs b/src/main.rs index f4490ac..29d32a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,7 +44,7 @@ async fn main() { let basic_path = Arc::new(args[1].clone()); */ let pool = MySqlPoolOptions::new() - .connect("mysql://user:pass@address/Blog") + .connect("mysql://wukong:wk(2024)@47.92.236.188/Blog") .await .expect("failed to connect database."); diff --git a/src/scheduler_job/article_read_time_scheduler_job.rs b/src/scheduler_job/article_read_time_scheduler_job.rs index 4be3bbc..304b71f 100644 --- a/src/scheduler_job/article_read_time_scheduler_job.rs +++ b/src/scheduler_job/article_read_time_scheduler_job.rs @@ -12,12 +12,16 @@ use std::{ task::{Context, Poll}, }; +use tokio::runtime::Runtime; + use sqlx::MySqlPool; use job_scheduler::{Job, JobScheduler}; +use crate::entity::access_count::AccessCount; + pub struct ArticleReadTimeSchedulerJob { - articls_click_vue: Arc>>, // 使用 Arc 允许多个所有权 + articls_click_vue: Arc>>, db_pool: MySqlPool, } @@ -35,20 +39,33 @@ impl ArticleReadTimeSchedulerJob { pub fn start(&self) { let articls_click_vue = Arc::clone(&self.articls_click_vue); - let _ = thread::spawn(move || { - let mut sched = JobScheduler::new(); + let db_pool = self.db_pool.clone(); // 克隆数据库连接池 - sched.add(Job::new("1/10 * * * * *".parse().unwrap(), || { + thread::spawn(move || { + let mut sched = JobScheduler::new(); + sched.add(Job::new("1/10 * * * * *".parse().unwrap(), move || { println!("hello job scheduler."); - for i in articls_click_vue.read().unwrap().iter() { - println!("{}", i.load(std::sync::atomic::Ordering::SeqCst)); - } + + // 创建 Tokio 运行时以运行异步代码 + let runtime = Runtime::new().expect("Unable to create Tokio runtime"); + runtime.block_on(async { + for i in articls_click_vue.read().unwrap().iter() { + println!("{}", i.load(Ordering::SeqCst)); + let account_count = AccessCount::new("", 1, 10); + + if let Err(e) = account_count.update(&db_pool).await { + eprintln!("Error updating access count: {}", e); + } + } + + // 清空重置 articls_click_vue + // todo + }); })); - //清空重置articls_click_vue - //todo + loop { sched.tick(); - std::thread::sleep(Duration::from_millis(500)); + thread::sleep(Duration::from_millis(500)); } }); } diff --git a/tklogsize.txt b/tklogsize.txt new file mode 100644 index 0000000..22603a1 --- /dev/null +++ b/tklogsize.txt @@ -0,0 +1,39 @@ +[INFO] 22:01:25 main.rs 36:Current workspace:: {:?}/home/owen/code/rust_project/axum-hello +[INFO] 22:03:10 main.rs 36:Current workspace:: {:?}/home/owen/code/rust_project/axum-hello +[INFO] 22:03:44 main.rs 102:page_num: 1 +[INFO] 22:03:44 main.rs 105:the offset is {}0 +[INFO] 22:03:45 main.rs 116:total: 3 +[INFO] 22:03:45 main.rs 122:position: 0 +[INFO] 22:03:45 main.rs 122:position: 1 +[INFO] 22:03:45 main.rs 122:position: 2 +[INFO] 22:03:48 main.rs 134:the article id is 2 +[INFO] 22:03:59 main.rs 134:the article id is 4 +[INFO] 22:04:12 main.rs 134:the article id is 1 +[INFO] 22:04:24 main.rs 134:the article id is 1 +[INFO] 22:04:28 main.rs 134:the article id is 1 +[INFO] 22:30:08 main.rs 36:Current workspace:: {:?}/home/owen/code/rust_project/axum-hello +[INFO] 22:37:49 main.rs 36:Current workspace:: {:?}/home/owen/code/rust_project/axum-hello +[INFO] 22:57:14 main.rs 36:Current workspace:: {:?}/home/owen/code/rust_project/axum-hello +[INFO] 22:58:56 main.rs 36:Current workspace:: {:?}/home/owen/code/rust_project/axum-hello +[INFO] 23:00:24 main.rs 36:Current workspace:: {:?}/home/owen/code/rust_project/axum-hello +[INFO] 23:01:56 main.rs 134:the article id is 1 +[INFO] 23:01:57 main.rs 134:the article id is 1 +[INFO] 23:01:57 main.rs 134:the article id is 1 +[INFO] 23:01:58 main.rs 134:the article id is 1 +[INFO] 23:01:58 main.rs 134:the article id is 1 +[INFO] 23:02:53 main.rs 36:Current workspace:: {:?}/home/owen/code/rust_project/axum-hello +[INFO] 23:03:04 main.rs 134:the article id is 1 +[INFO] 23:03:05 main.rs 134:the article id is 1 +[INFO] 23:03:44 main.rs 36:Current workspace:: {:?}/home/owen/code/rust_project/axum-hello +[INFO] 23:03:46 main.rs 134:the article id is 1 +[INFO] 23:03:47 main.rs 134:the article id is 1 +[INFO] 23:04:27 main.rs 134:the article id is 2 +[INFO] 23:04:30 main.rs 134:the article id is 4 +[INFO] 23:05:58 main.rs 36:Current workspace:: {:?}/home/owen/code/rust_project/axum-hello +[INFO] 23:06:00 main.rs 134:the article id is 4 +[INFO] 23:06:58 main.rs 36:Current workspace:: {:?}/home/owen/code/rust_project/axum-hello +[INFO] 23:08:47 main.rs 36:Current workspace:: {:?}/home/owen/code/rust_project/axum-hello +[INFO] 23:09:15 main.rs 134:the article id is 4 +[INFO] 23:09:50 main.rs 36:Current workspace:: {:?}/home/owen/code/rust_project/axum-hello +[INFO] 23:13:57 main.rs 36:Current workspace:: {:?}/home/owen/code/rust_project/axum-hello +[INFO] 23:15:00 main.rs 36:Current workspace:: {:?}/home/owen/code/rust_project/axum-hello -- Gitee From b8eb500ff60c3b1f0f182dd816e1eb49d5e6aa62 Mon Sep 17 00:00:00 2001 From: "owen.chao" Date: Tue, 17 Dec 2024 18:56:46 +0800 Subject: [PATCH 14/14] when id exists, then update the access_count --- src/entity/access_count.rs | 28 ++++++++----- .../article_read_time_scheduler_job.rs | 39 +++++++++++-------- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/entity/access_count.rs b/src/entity/access_count.rs index b9dd5a8..84c81c7 100644 --- a/src/entity/access_count.rs +++ b/src/entity/access_count.rs @@ -1,5 +1,6 @@ use std::io::{ Error, ErrorKind}; use sqlx::MySqlPool; +use tklog::error; #[derive(Clone)] #[derive(Debug)] @@ -17,22 +18,31 @@ impl<'a> AccessCount<'a> { total_times, } } + + pub async fn update(&self, pool :&MySqlPool) ->Result<(), Error> { println!("execute update ---------------------{}", &self.total_times); let result_update = sqlx::query( - r#" - UPDATE access_count - SET total_times =total_times+1 - WHERE article_id = 4"# - ) - //.bind(&self.total_times) - //.bind(&self.article_id) - .execute(pool).await; + r#" + INSERT INTO access_count (url, article_id, total_times, create_by, create_on, modify_by, modify_on) + VALUES (?, ?, ?, 'Scheduelr', NOW(), 'Scheduelr', NOW()) + ON DUPLICATE KEY UPDATE + total_times = total_times + VALUES(total_times), + modify_by = 'Scheduelr', + modify_on = now(), + create_by = 'Scheduelr', + create_on = now()"# + ) + .bind(self.url) + .bind(self.article_id) + .bind(self.total_times) + .execute(pool) + .await; match result_update { Ok(_) => {Ok(())}, Err(e) => { - eprintln!("error .............."); + error!(e.to_string()); Err(Error::new(ErrorKind::Other, e.to_string())) }, } diff --git a/src/scheduler_job/article_read_time_scheduler_job.rs b/src/scheduler_job/article_read_time_scheduler_job.rs index 304b71f..ce73a47 100644 --- a/src/scheduler_job/article_read_time_scheduler_job.rs +++ b/src/scheduler_job/article_read_time_scheduler_job.rs @@ -11,6 +11,7 @@ use std::{ fmt, task::{Context, Poll}, }; +use tklog::error; use tokio::runtime::Runtime; @@ -40,28 +41,34 @@ impl ArticleReadTimeSchedulerJob { pub fn start(&self) { let articls_click_vue = Arc::clone(&self.articls_click_vue); let db_pool = self.db_pool.clone(); // 克隆数据库连接池 + // 创建一个全局的 Tokio 运行时实例以供重用。 + let runtime = Runtime::new().expect("Unable to create Tokio runtime"); thread::spawn(move || { let mut sched = JobScheduler::new(); sched.add(Job::new("1/10 * * * * *".parse().unwrap(), move || { println!("hello job scheduler."); - // 创建 Tokio 运行时以运行异步代码 - let runtime = Runtime::new().expect("Unable to create Tokio runtime"); - runtime.block_on(async { - for i in articls_click_vue.read().unwrap().iter() { - println!("{}", i.load(Ordering::SeqCst)); - let account_count = AccessCount::new("", 1, 10); - - if let Err(e) = account_count.update(&db_pool).await { - eprintln!("Error updating access count: {}", e); - } - } - - // 清空重置 articls_click_vue - // todo - }); - })); + // 使用之前创建的运行时实例。 + runtime.block_on(async { + if let Ok(mut vec) = articls_click_vue.write() { + for (idx, value) in vec.iter_mut().enumerate() { + println!("{}", idx); + let click_times :i32= value.load(Ordering::SeqCst).into(); + if click_times >1 { + let account_count = AccessCount::new("", idx.try_into().unwrap(), click_times); + if let Err(e) = account_count.update(&db_pool).await { + eprintln!("Error updating access count: {}", e); + } + value.store(0, Ordering::SeqCst); + } + + } + } else { + error!("Failed to acquire write lock on articls_click_vue."); + } + }); + })); loop { sched.tick(); -- Gitee