init project

This commit is contained in:
chenwj 2024-07-30 14:29:02 +08:00
commit 95d8f7de3b
13 changed files with 3819 additions and 0 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
DATABASE_URL=mysql://root:Chenweijia113!@localhost/lottery

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

3427
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

16
Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "sched_tasks_rs"
version = "0.1.0"
edition = "2021"
[dependencies]
chrono = "0.4.38"
clokwerk = "0.4.0"
log = "0.4.22"
env_logger = "0.9"
openssl = { version = "0.10.66", features = ["vendored"] }
regex = "1.10.5"
reqwest = { version = "0.12.5", features = ["gzip"] }
scraper = "0.19.1"
sea-orm = { version = "0.12", features = [ "sqlx-mysql", "runtime-tokio-rustls", "macros" ]}
tokio = { version = "1.39.2", features = ["full"] }

16
Dockerfile Normal file
View File

@ -0,0 +1,16 @@
FROM hub.airpig.cn/library/alpine:latest
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
&& apk update && apk add tzdata \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone \
&& apk del tzdata
ENV RUST_LOG=info
ENV RUST_ENV=prod
COPY ./target/x86_64-unknown-linux-musl/release/sched_tasks_rs /bin/sched_tasks_rs
ENTRYPOINT ["/bin/sched_tasks_rs"]

94
src/main.rs Normal file
View File

@ -0,0 +1,94 @@
use std::{thread, time::Duration};
use chrono::format;
use clokwerk::{Scheduler, TimeUnits, Job};
use log::{info, error};
mod tasks;
use tasks::*;
fn main() {
env_logger::init();
let mut scheduler = Scheduler::new();
let fmt = "%Y年%m月%d日 %H:%M:%S";
let notify_url = "https://ding.airpig.cn/QfagvFa7u323xUS5yRbFR3";
scheduler.every(1.day()).at("00:01:00").run(move|| {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let res = get_data_from_500_com("pls").await;
let msg;
match res {
Ok(_) => {
msg = format!("{}: 排列3数据采集完毕", chrono::Local::now().format(fmt));
info!("{}", msg);
},
Err(e) => {
msg = format!("{}: 排列3数据采集失败: {}", chrono::Local::now().format(fmt), e);
error!("{}", msg);
}
}
let notify = reqwest::get(format!("{}/{}", notify_url, msg)).await.unwrap();
if notify.status().is_success() {
info!("{}: 排列3数据采集通知发送成功", chrono::Local::now().format(fmt));
} else {
error!("{}: 排列3数据采集通知发送失败", chrono::Local::now().format(fmt));
}
})
});
scheduler.every(1.day()).at("00:01:15").run(move|| {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let res = get_data_from_500_com("plw").await;
let msg;
match res {
Ok(_) => {
msg = format!("{}: 排列5数据采集完毕", chrono::Local::now().format(fmt));
info!("{}", msg);
},
Err(e) => {
msg = format!("{}: 排列5数据采集失败: {}", chrono::Local::now().format(fmt), e);
error!("{}", msg);
}
}
let notify = reqwest::get(format!("{}/{}", notify_url, msg)).await.unwrap();
if notify.status().is_success() {
info!("{}: 排列5数据采集通知发送成功", chrono::Local::now().format(fmt));
} else {
error!("{}: 排列5数据采集通知发送失败", chrono::Local::now().format(fmt));
}
})
});
scheduler.every(1.day()).at("00:01:30").run(move|| {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let res = get_data_from_500_com("sd").await;
let msg;
match res {
Ok(_) => {
msg = format!("{}: 福彩3D数据采集完毕", chrono::Local::now().format(fmt));
info!("{}", msg);
},
Err(e) => {
msg = format!("{}: 福彩3D数据采集失败: {}", chrono::Local::now().format(fmt), e);
error!("{}", msg);
}
}
let notify = reqwest::get(format!("{}/{}", notify_url, msg)).await.unwrap();
if notify.status().is_success() {
info!("{}: 福彩3D数据采集通知发送成功", chrono::Local::now().format(fmt));
} else {
error!("{}: 福彩3D数据采集通知发送失败", chrono::Local::now().format(fmt));
}
})
});
info!("{}: 开始运行定时任务", chrono::Local::now().format("%Y年%m月%d日 %H:%M:%S"));
loop {
scheduler.run_pending();
thread::sleep(Duration::from_millis(100));
}
}

143
src/tasks/get_data.rs Normal file
View File

@ -0,0 +1,143 @@
use std::{env, time::Duration};
use chrono::{Local, NaiveDate};
use regex::Regex;
use scraper::{Html, Selector};
use sea_orm::{ActiveModelTrait, ActiveValue::Set, ConnectOptions, Database};
use crate::model::{sd, pls, plw};
pub async fn get_data_from_500_com(issue_type: &str) -> Result<(), String> {
// 数据库连接操作
let db_url;
if env::var("RUST_ENV").unwrap_or_else(|_|"dev".to_string()) == "dev" {
db_url = "mysql://root:Chenweijia113!@localhost/lottery";
} else {
db_url = "mysql://root:Chenweijia113!@mysql/lottery";
}
let mut opt = ConnectOptions::new(db_url);
opt.max_connections(100)
.min_connections(5)
.connect_timeout(Duration::from_secs(8))
.acquire_timeout(Duration::from_secs(8))
.idle_timeout(Duration::from_secs(8))
.max_lifetime(Duration::from_secs(8))
.sqlx_logging(true)
.sqlx_logging_level(log::LevelFilter::Info)
.set_schema_search_path("my_schema");
let db = Database::connect(opt).await.expect("Failed to connect to database");
let url = format!("https://kaijiang.500.com/{}.shtml", issue_type);
let client = reqwest::Client::new();
let res = client.get(&url).send().await.expect("Failed to send request");
println!("url: {}, status: {}", url, res.status());
if res.status().is_success() {
let document = res.text_with_charset("gb2312").await.unwrap();
let html = Html::parse_document(&document);
let issue_selector = Selector::parse(r#"td.td_title01 > span.span_left > a > font.cfont2 > strong"#).expect("selector error");
let issue_iter = html.select(&issue_selector).map(|x| {x.inner_html()});
let issue: String = issue_iter.collect();
let date_selector = Selector::parse(r#"td.td_title01 > span.span_right"#).expect("selector error");
let date_str = html.select(&date_selector).map(|x| {x.inner_html()}).collect::<String>();
let re = Regex::new(r"开奖日期:(.*) 兑奖截止日期:(.*)").unwrap();
let Some(draw_date) = re.captures(date_str.as_str()) else { return Err(String::from("日期解析错误")) };
let draw_date = draw_date.get(1).unwrap().as_str();
let date = NaiveDate::parse_from_str(draw_date, "%Y年%m月%d日").expect("Failed to parse date");
let ball_selector = Selector::parse(r#"li.ball_orange"#).expect("selector error");
let ball_iter = html.select(&ball_selector).map(|x| {x.inner_html()});
let ball: String = ball_iter.collect();
let numbers : Vec<i32> = ball.chars().map(|c| c.to_digit(10).unwrap() as i32).collect();
let mut seen = std::collections::HashSet::new();
let unique_numbers: Vec<_> = numbers.clone().into_iter().filter(|&x| seen.insert(x)).collect(); // 只有当元素被成功插入时(即它是唯一的),才包含它
// let max = numbers.iter().max().unwrap_or(&0);
// let min = numbers.iter().min().unwrap_or(&0);
let sum = numbers.iter().sum::<i32>();
let code_small = numbers.iter().filter(|&&x| x < 5).count() as i32;
let code_big = numbers.iter().filter(|&&x| x >= 5).count() as i32;
let code_single = numbers.iter().filter(|&&x| x % 2 == 1).count() as i32;
let code_double = numbers.iter().filter(|&&x| x % 2 == 0).count() as i32;
if issue_type == "sd" {
let group_type = if unique_numbers.len() == 3 { 6 } else if unique_numbers.len() == 2{ 3 } else {1};
let new_model = sd::ActiveModel {
draw_issue: Set(Some(issue)),
draw_code: Set(Some(ball)),
draw_date: Set(Some(date)),
created_at: Set(Some(Local::now().naive_local())),
sum_num: Set(Some(sum)),
code_small: Set(Some(code_small)),
code_big: Set(Some(code_big)),
code_single: Set(Some(code_single)),
code_double: Set(Some(code_double)),
hundred: Set(Some(numbers[0])),
ten: Set(Some(numbers[1])),
one: Set(Some(numbers[2])),
sum_hundred_one: Set(Some(numbers[0] + numbers[2])),
sum_hundred_ten: Set(Some(numbers[0] + numbers[1])),
sum_ten_one: Set(Some(numbers[1] + numbers[2])),
group_type: Set(Some(group_type)),
..Default::default()
};
new_model.insert(&db).await.expect("insert error");
} else if issue_type == "pls" {
let group_type = if unique_numbers.len() == 3 { 6 } else if unique_numbers.len() == 2{ 3 } else {1};
let new_model = pls::ActiveModel {
draw_issue: Set(Some(issue)),
draw_code: Set(Some(ball)),
draw_date: Set(Some(date)),
created_at: Set(Some(Local::now().naive_local())),
sum_num: Set(Some(sum)),
code_small: Set(Some(code_small)),
code_big: Set(Some(code_big)),
code_single: Set(Some(code_single)),
code_double: Set(Some(code_double)),
hundred: Set(Some(numbers[0])),
ten: Set(Some(numbers[1])),
one: Set(Some(numbers[2])),
sum_hundred_one: Set(Some(numbers[0] + numbers[2])),
sum_hundred_ten: Set(Some(numbers[0] + numbers[1])),
sum_ten_one: Set(Some(numbers[1] + numbers[2])),
group_type: Set(Some(group_type)),
..Default::default()
};
new_model.insert(&db).await.expect("insert error");
} else if issue_type == "plw" {
let new_model = plw::ActiveModel {
draw_issue: Set(Some(issue)),
draw_code: Set(Some(ball)),
draw_date: Set(Some(date)),
created_at: Set(Some(Local::now().naive_local())),
sum_num: Set(Some(sum)),
code_small: Set(Some(code_small)),
code_big: Set(Some(code_big)),
code_single: Set(Some(code_single)),
code_double: Set(Some(code_double)),
ten_thousand: Set(Some(numbers[0])),
thousand: Set(Some(numbers[1])),
hundred: Set(Some(numbers[2])),
ten: Set(Some(numbers[3])),
one: Set(Some(numbers[4])),
sum_hundred_one: Set(Some(numbers[2] + numbers[4])),
sum_hundred_ten: Set(Some(numbers[2] + numbers[3])),
sum_ten_one: Set(Some(numbers[3] + numbers[4])),
sum_first_3: Set(Some(numbers[0] + numbers[1] + numbers[2])),
sum_next_3: Set(Some(numbers[2] + numbers[3] + numbers[4])),
..Default::default()
};
new_model.insert(&db).await.expect("insert error");
}
Ok(())
} else {
Err(String::from("获取数据失败"))
}
}

4
src/tasks/mod.rs Normal file
View File

@ -0,0 +1,4 @@
pub mod get_data;
pub mod model;
pub use get_data::get_data_from_500_com;

7
src/tasks/model/mod.rs Normal file
View File

@ -0,0 +1,7 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15
pub mod prelude;
pub mod sd;
pub mod pls;
pub mod plw;

35
src/tasks/model/pls.rs Normal file
View File

@ -0,0 +1,35 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "pls")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub draw_issue: Option<String>,
pub draw_date: Option<Date>,
pub draw_code: Option<String>,
pub hundred: Option<i32>,
pub ten: Option<i32>,
pub one: Option<i32>,
pub group_type: Option<i32>,
pub group_3_span: Option<i32>,
pub code_big: Option<i32>,
pub code_small: Option<i32>,
pub code_single: Option<i32>,
pub code_double: Option<i32>,
pub sum_num: Option<i32>,
pub sum_num_span: Option<i32>,
pub sum_ten_one: Option<i32>,
pub sum_hundred_ten: Option<i32>,
pub sum_hundred_one: Option<i32>,
pub sum_single_double: Option<i32>,
pub sum_big_small: Option<i32>,
pub created_at: Option<DateTime>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}

34
src/tasks/model/plw.rs Normal file
View File

@ -0,0 +1,34 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "plw")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub draw_issue: Option<String>,
pub draw_date: Option<Date>,
pub draw_code: Option<String>,
pub ten_thousand: Option<i32>,
pub thousand: Option<i32>,
pub hundred: Option<i32>,
pub ten: Option<i32>,
pub one: Option<i32>,
pub code_big: Option<i32>,
pub code_small: Option<i32>,
pub code_single: Option<i32>,
pub code_double: Option<i32>,
pub sum_num: Option<i32>,
pub sum_ten_one: Option<i32>,
pub sum_hundred_ten: Option<i32>,
pub sum_hundred_one: Option<i32>,
pub sum_first_3: Option<i32>,
pub sum_next_3: Option<i32>,
pub created_at: Option<DateTime>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -0,0 +1,6 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15
#[allow(unused_imports)]
pub use super::sd::Entity as Sd;
pub use super::pls::Entity as Pls;
pub use super::plw::Entity as Plw;

35
src/tasks/model/sd.rs Normal file
View File

@ -0,0 +1,35 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "sd")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub draw_issue: Option<String>,
pub draw_date: Option<Date>,
pub draw_code: Option<String>,
pub hundred: Option<i32>,
pub ten: Option<i32>,
pub one: Option<i32>,
pub group_type: Option<i32>,
pub group_3_span: Option<i32>,
pub code_big: Option<i32>,
pub code_small: Option<i32>,
pub code_single: Option<i32>,
pub code_double: Option<i32>,
pub sum_num: Option<i32>,
pub sum_num_span: Option<i32>,
pub sum_ten_one: Option<i32>,
pub sum_hundred_ten: Option<i32>,
pub sum_hundred_one: Option<i32>,
pub sum_single_double: Option<i32>,
pub sum_big_small: Option<i32>,
pub created_at: Option<DateTime>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}