Android后端签到flask迁移到rust的axum的过程-签到性能和便携

发布于:2024-12-18 ⋅ 阅读:(98) ⋅ 点赞:(0)

本次变更了以下内容:

  • 为了使用之前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是一个总页面模板.