本次变更了以下内容:
- 为了使用之前ip2sta的ip到端点名的python,dic变量,将其存入redis hashset.使用地址/api/ip2dic 手动执行之.并且定义在/station/init,这个每天初始化redis的路径下.
- 在rust axum使用redis 连接池在test中 ip2dic,IP转端点名,转本日此端网址.
- 在前端的人名下使用了乒乓操作.点击状态切换,并把签到的人汇总到请假列. 可以请假和取消请假. 三个集合存数据.
*. 1端点对应的人名集合,key day/端点名
*. 2.签到的集合,key check:端点名
*. 3,请假的集合 key check:端点名:thin (hashset,可以存放具体内容,但没使用) - 最后是调整caddy的反代,需要使用 个别路径的跳转,其中rust使用全新的根路径下的子路径,但是android入口也就是获得IP并且ip2sta转到端点名的路径,定义到新的rust接口.剩下的由rust实现,移动端的功能.需要使用以下语法快.在rust规划中应该加入somepath目录,这样,调试和发布,就能通用了.
handle_path /somepath/*
rewrite * /somerust{path}
上个文章给出了示例redis连接池.本次在它基础扩展了几乎全部adroid,app,webkit所使用网页的实现方式,切换为axum.主要逻辑从python直接迁移.说下缺点:
- js,html,rust, 互相掺和在一起.至今也不愿隔离.由于include ,js,bootstrap,css,.这些都在运行环境,所以的cargo run的运行时虽然可以绑定redis,但是静态文件并不能直达.需要一个解决的办法.
- 路径的话,归结为一个统一子路径,这样容易迁移,尤其在反向代理的时候,暴露一个统一子路径就比较好了.
- route不能如flask, /test /test/ test/:arg 定向到一个函数fn, 使用变量赋值缺省值的方式.
- 路径下函数的调用,不太知道怎么做,所以功能的分离做起来挺费劲的.
- 关于参数的传递有点太过神奇,不知道怎么就过去了,但是要想formt!()宏的第一个参数使用,一段文件里的内容最后怎么也无法实现,最后用了mut string的 replace.
速度和易用性肯定有提升,但是意义不算大.还有就是,可执行文件5.7M,不要建立python.环境.小功率设备也可以.这是全部的好处了.以下是总代码.
Cargo.toml
[package]
name = "hello-axum"
version = "0.1.0"
edition = "2021"[dependencies]
axum = "0.8.0-alpha.1"
bb8 = "0.8.5"
bb8-redis = "0.17.0"
redis = "0.27.2"
tokio = { version = "1.0", features = ["full", "macros", "rt-multi-thread"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
chrono = "0.4.39"
main.rc
use axum::{extract::{FromRef, FromRequestParts, Path, State },http::{header::HeaderValue,request::{self, Parts}, StatusCode},response::Html,routing::get,http::HeaderMap,response::Redirect,Router,
};
use std::{any::{type_name,type_name_of_val, TypeId}, result};
use bb8::{Pool, PooledConnection};
use bb8_redis::RedisConnectionManager;
use redis::AsyncCommands;
use tracing_subscriber::{fmt::format, layer::SubscriberExt, util::SubscriberInitExt};use chrono::Local;
use bb8_redis::bb8;#[tokio::main]
async fn main() {tracing_subscriber::registry().with(tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| format!("{}=debug", env!("CARGO_CRATE_NAME")).into()),).with(tracing_subscriber::fmt::layer()).init();tracing::debug!("connecting to redis");let redurl="redis://ip:6379/9";let manager = RedisConnectionManager::new(redurl).unwrap();let pool = bb8::Pool::builder().build(manager).await.unwrap();{// ping the database before startinglet mut conn = pool.get().await.unwrap();conn.set::<&str, &str, ()>("foo", "barr").await.unwrap();let result: String = conn.get("foo").await.unwrap();assert_eq!(result, "barr");}tracing::debug!("successfully connected to redis and pinged it");// build our application with some routeslet app = Router::new().route("/",get(using_connection_pool_extractor),// post.(using_connection_extractor),)//.route("/rsta/{day}/{sta}", get( bsta)).route("/sta/{day}/{sta}/{person}", get( bsta)).route("/check/{sta}/{person}",get(check)).route("/test/{person}",get(test)).route("/test/",get(test)).route("/thincheck/{sta}/{person}",get(thincheck)).route("/sta/ip2sta",get(using_connection_extractor)).with_state(pool);// run itlet listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();tracing::debug!("listening on {}", listener.local_addr().unwrap());axum::serve(listener, app).await.unwrap();
}type ConnectionPool = Pool<RedisConnectionManager>;async fn using_connection_pool_extractor(State(pool): State<ConnectionPool>,
) -> Result<String, (StatusCode, String)> {let mut conn = pool.get().await.map_err(internal_error)?;let result: String = conn.get("foo").await.map_err(internal_error)?;Ok(format!("端点:{}",result))
}
// my first python route async fn test(State(pool): State<ConnectionPool>,headers: HeaderMap,Path((person)):Path<(String)>
) -> Result<Redirect, (StatusCode, String)> {let mut conn = pool.get().await.map_err(internal_error)?;// let result:Vec<String>= conn.keys("*").await.map_err(internal_error)?;//let key=format!("2024-10-21/{}","衡水北");// let key=&format!("check:{}","衡水北") ;// let result:Vec<String>= conn.lrange(key,0,-1).await.map_err(internal_error)?;// let result :Vec<String>= conn.smembers(key).await.map_err(internal_error)?;// // Ok(format!("{:?}",headers))let mut strip="10.180.145.40:544545";if let Some(ip) = headers.get("X-Forwarded-For") {strip = std::str::from_utf8(ip.as_bytes()).map_err(internal_error)?;}
if let Some(ip) = headers.get("X-Real-IP") {strip = std::str::from_utf8(ip.as_bytes()).map_err(internal_error)?;} let mut cip =String::from(strip);cip.truncate(cip.find(":").unwrap_or(cip.len()));let ipin=conn.hexists("ip2sta", &cip).await.map_err(internal_error)?;let mut sta=String::from("调度");if ipin{sta= conn.hget("ip2sta",&cip).await.map_err(internal_error)?;}// datetime.date.today().strftime("%Y-%m-%d") let now = Local::now();let formatted = now.format("%Y-%m-%d").to_string();Ok(Redirect::to(&format!("/rk/sta/{}/{}/{}",formatted,&sta,&person)))
}
async fn bsta(State(pool): State<ConnectionPool>,Path((day, sta,person )): Path<(String, String,String)>
) -> Result<Html<String>, (StatusCode, String)> {let mut conn = pool.get().await.map_err(internal_error)?;// let result:Vec<String>= conn.keys("*").await.map_err(internal_error)?;// let result:Vec<String>= conn.lrange(key,0,-1).await.map_err(internal_error)?;// let result :Vec<String>= conn.smembers(key).await.map_err(internal_error)?;let homebytes = include_bytes!("home.html");let mut homestr = String::from(std::str::from_utf8(homebytes).map_err(internal_error)?);let mut result=String::from("");if sta.contains("&"){for ista in sta.split("&"){result.push_str( stacheck(State(pool.clone()), &day, &ista).await?.as_str());}
}
else
{result.push_str( stacheck(State(pool.clone()), &day, &sta).await?.as_str());
} let cks=format!(r#"<script type="text/javascript"> function emitinfo(person,urlme){{console.log(person)window.golsocket.emit('mess', person)location.href=urlme// fechange(person,urlme)}}
window.onload = function() {{ document.getElementById("{person}").focus();
}}; </script> <body style="background: url('/images/backgroud.jpg') no-repeat center center fixed;-moz-background-size: cover;-webkit-background-size: cover;-o-background-size: cover;background-size: cover;
" > <div class="page-header" style="width: 100%;"><h3 class="opacity-75" align=center>{sta} 会 议 签 到</h3><p align=right id=day >{day}</p> </div> <p>{result}</p><div style=" display: flex;justify-content: right;align-items: right;width:80%;height:70%;"><h2><span class="label label-success h5">[未签到]回到会议,将弹框⏏︎到此签到⬆️,请试按此键☛</span> </h2><img style="width: auto" src='/images/docu2.jpg' alg="some"/></div> "#);let mark=if result.contains('V') {"nill"} else { "null"} ; let sec = &homestr.find("{mark}").unwrap();homestr.replace_range(sec..&(sec+6),&mark);// let homestr2= &homestr1.replace("{mark}", mark);let sec = &homestr.find("{mainstr}").unwrap();homestr.replace_range(sec..&(sec+9),&cks);Ok(Html(String::from(homestr)))
}async fn stacheck( State(pool): State<ConnectionPool> ,day:&str,sta:&str
)-> Result<String, (StatusCode, String)> {let mut conn = pool.get().await.map_err(internal_error)?;// let mut conn = pool.get().await.map_err(internal_error)?;let result:Vec<String>= conn.keys("*").await.map_err(internal_error)?;let key=format!("{day}/{sta}" );let ckey=format!("check:{}",sta);let tkey=&format!("check:{}:thin",sta);let persons:Vec<String>= conn.lrange(key,0,-1).await.map_err(internal_error)?;let checks :Vec<String>= conn.smembers(ckey).await.map_err(internal_error)?;let thins :Vec<String>=conn.hkeys(tkey).await.map_err(internal_error)?;// let re= conn.del(tkey ).await.map_err(internal_error)?;// Ok(format!("{:?}",thins))let info= if checks.len()==0 {"btn-warning"}else {"btn-light "};let mut re=format!(r#"<li class="list-group-item lh-sm " style="height: 45px" ><span class= "btn {info}"> {sta}: </span>"#);for i in persons{
//<a href="/thinks/{sta}/{i}">有事</a> let mark= if (&checks).contains(&i) {r#""green">V</font>]</a>"# }else {r#""red">X</font>]</a>"#}; re.push_str(& format!(r#"<a class="btn btn-light" href='#' οnclick='emitinfo("{i}","/rk/check/{sta}/{i}")' id={i}> {i} [<font color={mark}<space/> "#));}re.push_str(r#"{<span class= "btn {info}">有事请单击:</span>"#);for i in &checks{let mark= if (&thins).contains(i) {r#""blue">O</font>]</a>"# } else {r#""green">V</font>]</a>"# }; re.push_str(& format!(r#"<a class="btn btn-light" href='#' οnclick='emitinfo("{i}","/rk/thincheck/{sta}/{i}" )' id="{i}s"> {i} [<font color={mark}<space/> "#));}re.push_str(&format!(r#"}}<a class="lable lable-light opacity-75 " href='#'b sta="{sta}" id="{sta}">[more]</a><space/></li>"#));//<a class="lable lable-light opacity-75 " href="#" sta="{sta}" id="{sta}Ok(format!("{}",re.as_str()))
}async fn thincheck(State(pool): State<ConnectionPool>,headers: HeaderMap,Path((sta, person)): Path<(String, String)>
) -> Result<Redirect, (StatusCode, String)> {let mut conn = pool.get().await.map_err(internal_error)?;let tkey=&format!("check:{}:thin",sta);let onthins = conn.hexists(tkey,&person).await.map_err(internal_error)?;if onthins { let result: String =conn.hdel(tkey,&person).await.map_err(internal_error)?;} else {let result: String = conn.hset(tkey,&person,"thin").await.map_err(internal_error)?;
}
//let def=HeaderValue::from_str(&format!("/rk/test/{}s",&person)).unwrap();
//let rurl=headers.get("referer").unwrap_or(&def).to_str().unwrap_or_default();
let rurl=&format!("/rk/test/{}s",&person);
Ok( Redirect::to( rurl))
}
async fn check(State(pool): State<ConnectionPool>,headers: HeaderMap,Path((sta, person)): Path<(String, String)>
) -> Result<Redirect, (StatusCode, String)> {let mut conn = pool.get().await.map_err(internal_error)?;let ckey=&format!("check:{}",sta);let tkey=&format!("check:{}:thin",sta);// conn.del(ckey ).await.map_err(internal_error)?;let ischeck = conn.sismember(ckey,&person).await.map_err(internal_error)?;if ischeck {let result: String =conn.srem(ckey,&[&person]).await.map_err(internal_error)?;let result: String =conn.hdel(tkey,&person).await.map_err(internal_error)?;} else {let result: String = conn.sadd(ckey,&[&person]).await.map_err(internal_error)?;
}// let def=HeaderValue::from_str(&format!("/rk/test/{}",&person)).unwrap();//let rurl=headers.get("referer").unwrap_or( &def).to_str().unwrap_or_default();let rurl=&format!("/rk/test/{}",&person);Ok( Redirect::to( rurl))
}
// we can also write a custom extractor that grabs a connection from the pool
// which setup is appropriate depends on your application
struct DatabaseConnection(PooledConnection<'static, RedisConnectionManager>);impl<S> FromRequestParts<S> for DatabaseConnection
whereConnectionPool: FromRef<S>,S: Send + Sync,
{type Rejection = (StatusCode, String);async fn from_request_parts( _parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {let pool = ConnectionPool::from_ref(state);let conn = pool.get_owned().await.map_err(internal_error)?;Ok(Self(conn))}
}async fn using_connection_extractor(DatabaseConnection(mut conn): DatabaseConnection,
) -> Result<String, (StatusCode, String)> {conn.set::<&str, &str, ()>("station", "wjc,zhw,sd").await.unwrap();let result: String = conn.hgetall("ip2sta").await.map_err(internal_error)?;// let result: String = conn.hset("ip2sta","10.180.133.72","宫东").await.map_err(internal_error)?;Ok(format!("{:?}",result))
} /// Utility function for mapping any error into a `500 Internal Server Error`
/// response.
fn internal_error<E>(err: E) -> (StatusCode, String)
whereE: std::error::Error,
{(StatusCode::INTERNAL_SERVER_ERROR, err.to_string())
}
这里的home.html是一个总页面模板.