本次变更了以下内容:
- 为了使用之前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 starting
let 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 routes
let 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 it
let 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
where
ConnectionPool: 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)
where
E: std::error::Error,
{
(StatusCode::INTERNAL_SERVER_ERROR, err.to_string())
}
这里的home.html是一个总页面模板.