Przeglądaj źródła

若干调整,添加了分享操作流程的处理

zii 1 miesiąc temu
rodzic
commit
e9dba379fd
8 zmienionych plików z 287 dodań i 17 usunięć
  1. 75 6
      data_init.py
  2. 14 0
      docs/数据库设计.md
  3. 39 0
      src/api/device.rs
  4. 116 0
      src/api/flow_task.rs
  5. 9 3
      src/api/mod.rs
  6. 14 4
      src/api/user.rs
  7. 17 1
      src/datasource/sqlite.rs
  8. 3 3
      src/main.rs

+ 75 - 6
data_init.py

@@ -1,7 +1,11 @@
 import sqlite3
-
+# -----
+# 创建数据库
 curs = (conn:=sqlite3.connect('db.sqlite')).cursor()
-
+# --------
+# 表创建
+#
+# 创建用户表
 curs.execute('''
 create table if not exists user (
 id integer PRIMARY KEY,
@@ -14,13 +18,78 @@ lastlogin timestamp,
 isdelete integer not null default 0
 )
 ''').fetchall()
-
+# --------
+# 设备与区域有映射关系,其中设备最多映射两个区域,区域之间存在上下级关系
+#
+# 创建设备表
+curs.execute('''
+create table if not exists device (
+id integer PRIMARY KEY,
+name text not null,
+typo text not null,
+area integer not null default 0 -- 0:未分配到区域 其他:所在区域编号
+)
+''').fetchall()
+# 创建区域表
+curs.execute('''
+create table if not exists area (
+id integer PRIMARY KEY,
+name text not null,
+sub integer not null -- 0:无上级区域 其他:下级区域编号
+)
+''').fetchall()
+# 用户-设备映射表
+curs.execute('''
+create table if not exists map_user_device (
+id integer PRIMARY KEY,
+did integer not null,
+uid integer not null
+)
+''').fetchall()
+# 区域-设备映射表
+curs.execute('''
+create table if not exists map_area_device (
+id integer PRIMARY KEY,
+did integer not null,
+aid integer not null
+)
+''').fetchall()
+# 用户-区域映射表
+curs.execute('''
+create table if not exists map_user_area (
+id integer PRIMARY KEY,
+uid integer not null,
+aid integer not null
+)
+''').fetchall()
+curs.execute('''select * from user''').fetchall()
+# --------
+# 流程处理:分享与转移设备操作
+# 创建设备分享表
 curs.execute('''
-insert into user (uname,passwd,nickname) values ('root', 'e10adc3949ba59abbe56e057f20f883e', 'admin')
+create table if not exists flow_task_share (
+id integer PRIMARY KEY,
+did integer not null,
+typo integer not null,
+ticket integer not null,  -- 不需要uid, 因为分享不限用户
+createtime timestamp not null default current_timestamp,
+isdelete integer not null default 0, -- 0:未处理 1:处理完毕 2:超时未处理
+unique(ticket)
+)
 ''').fetchall()
+
+conn.commit()
+# -----
+# 表内容初始化
+#
+# 添加用户
 curs.execute('''
-insert into user (uname,passwd,nickname) values ('admin', 'e10adc3949ba59abbe56e057f20f883e', 'test')
+insert into user (uname,passwd,nickname) values 
+('root', 'e10adc3949ba59abbe56e057f20f883e', 'admin'),
+('admin', 'e10adc3949ba59abbe56e057f20f883e', 'test')
 ''').fetchall()
+conn.commit()
+
 curs.execute('''
-select * from user
+select * from flow_task_share
 ''').fetchall()

+ 14 - 0
docs/数据库设计.md

@@ -0,0 +1,14 @@
+# 数据库设计
+
+## 设备-区域-用户-用户组
+
+用户组=1-N=用户
+区域=1-N=设备
+用户=1-N=设备
+用户=1-N=区域
+
+## 设备转让/分享
+
+task - funA|funB
+
+deadline - delete

+ 39 - 0
src/api/device.rs

@@ -0,0 +1,39 @@
+use crate::AppState;
+use super::{JsonBack};
+use super::{token_fail,check_login, errcode0};
+use crate::datasource::{Datasource, SqliteParams};
+
+use axum::{Json,extract::State};
+
+#[derive(serde::Deserialize)]
+pub struct Dedit{
+    token: String,
+    id: u64,
+    name: Option<String>,
+    area0: Option<u64>,
+    area1: Option<u64>,
+}
+
+pub async fn d_edit(
+    State(state): State<AppState>,
+    Json(u): axum::extract::Json<Dedit>
+) -> Json<JsonBack> {
+    match check_login(&state, u.token).await {
+        Ok(_) => {},
+        Err(_) => {
+            return token_fail();
+        }
+    }
+    let mut params: SqliteParams = vec![Box::new(0)];
+    match state.db_lite.execute(format!("update device set isdelete=?{}{}{} where id=?",
+        if let Some(filter) = u.name {params.push(Box::new(filter));", name=?"} else {""},
+        if let Some(filter) = u.area0 {params.push(Box::new(filter));", name=?"} else {""},
+        if let Some(filter) = u.area1 {params.push(Box::new(filter));", name=?"} else {""},
+    ).as_str(), rusqlite::params_from_iter({params.push(Box::new(u.id));params})).await{
+        Ok(_) => {},
+        Err(_) => {
+            return Json(JsonBack{errcode: 3000, errmsg: Some("更新失败".to_string())});
+        }
+    }
+    errcode0()
+}

+ 116 - 0
src/api/flow_task.rs

@@ -0,0 +1,116 @@
+use crate::AppState;
+use super::{JsonBack, errcode0, token};
+use super::{token_fail,check_login};
+use crate::datasource::Datasource;
+
+use serde::{Serialize, Deserialize};
+use axum::{Json,extract::{State, Query}, http::HeaderMap};
+
+
+#[derive(Deserialize)]
+pub struct TaskOfDeviceShareOrTransfer{
+    token: String,
+    id: u64,
+    r#type: u8,
+}
+#[derive(Serialize)]
+pub struct UrlBack{
+    errcode: u16,
+    errmsg: Option<String>,
+    url: Option<String>,
+}
+pub async fn new_flow_task_share_device(
+    headers: HeaderMap,
+    State(state): State<AppState>,
+    Json(u): axum::extract::Json<TaskOfDeviceShareOrTransfer>
+) -> Json<UrlBack> {
+    match check_login(&state, u.token).await {
+        Ok(_) => {},
+        Err(_) => {
+            return Json(UrlBack{errcode: 3000, errmsg: Some(format!("鉴权失败: token失效")),url:None});
+        }
+    }
+    let mut tryno=0;
+    let ticket = token();
+    while tryno<5 && match state.db_lite.execute(
+        "insert into flow_task_share (did, typo, ticket)values (?,?,?)", 
+        (u.id,u.r#type,ticket.clone())).await{
+        Ok(_) => false,
+        Err(e) => 
+            if e==""{true}  // execute时如果遇到Unique异常时会将异常处理为空字符串
+            else{return Json(UrlBack{errcode: 3000, errmsg: Some(format!("分享流程创建失败: {e}")),url:None})},
+    }
+    {
+        tryno+=1;
+    };
+    if tryno==5{
+        return Json(UrlBack{errcode: 3000, errmsg: Some(format!("分享流程创建失败: 反复重试失败")),url: None});
+    }
+    
+    // 获取主机名
+    let host = headers.get("host")
+        .and_then(|hv| hv.to_str().ok())
+        .unwrap_or("localhost:3000");
+        
+    Json(UrlBack{errcode: 0, errmsg: None,url:Some(format!("http://{host}/api/flow/share/checkout?ticket={ticket}"))})
+}
+
+
+#[derive(Deserialize)]
+pub struct QueryParams {
+    ticket: String,
+}
+#[derive(Deserialize)]
+pub struct Ident{
+    token: String
+}
+pub async fn checkout_flow_task_of_share_device(
+    State(state): State<AppState>,
+    Query(params): Query<QueryParams>,
+    Json(u): Json<Ident>
+) -> Json<JsonBack> {
+    let uid = match check_login(&state, u.token).await {
+        Ok(id) => id,
+        Err(_) => {
+            return token_fail();
+        }
+    };
+    let (id, typo,interval) = match state.db_lite.query(
+        "select id,typo,strftime('%s','now')-strftime('%s',createtime) from flow_task_share where ticket=? and isdelete=0",
+        [params.ticket.clone()], |r|Ok((r.get::<usize, u64>(0)?,r.get::<usize, u8>(1)?,r.get::<usize, u64>(1)?))).await{
+        Ok(a) => a,
+        Err(_) => return Json(JsonBack{errcode: 3000, errmsg: Some(
+            "未找到分享流程".to_string()
+            // format!("未找到分享流程 {} {e}",params.ticket.clone())
+        )}),
+    };
+    if interval > 30*60 /* interval为当前时间减去createtime的秒数 */ {
+        match state.db_lite.execute(
+            "delete from flow_task_share where ticket=?", 
+            // "update flow_task_share set isdelete=1 where ticket=?", 
+            [params.ticket.clone()]).await{
+            Ok(_) => {},
+            Err(e) => return Json(JsonBack{errcode: 3001, errmsg: Some(format!("删除分享流程失败: {e}"))}),
+        };
+        return Json(JsonBack{errcode: 3001, errmsg: Some("分享流程已过期".to_string())});
+    }
+    if typo == 0{
+        match state.db_lite.execute("insert into map_user_area(uid,aid)values(?,?)", [uid, id]).await{
+            Ok(_) => {
+            },
+            Err(e) => return Json(JsonBack{errcode: 3002, errmsg: Some(format!("添加设备分享关系失败: {e}"))}),
+        };
+    }else if typo == 1 {
+        match state.db_lite.execute("insert into map_user_device(uid,did)values(?,?)", [uid, id]).await{
+            Ok(_) => {},
+            Err(e) => return Json(JsonBack{errcode: 3002, errmsg: Some(format!("添加设备分享关系失败: {e}"))}),
+        };    
+    } else {
+        return Json(JsonBack{errcode: 3003, errmsg: Some("分享流程类型错误".to_string())});
+    }
+    match state.db_lite.execute("delete from flow_task_share where ticket=?", [params.ticket.clone()]).await{
+        Ok(_) => {},
+        Err(_) => {}
+    }
+    errcode0()
+}

+ 9 - 3
src/api/mod.rs

@@ -1,5 +1,6 @@
 pub mod user;
 pub mod device;
+pub mod flow_task;
 #[cfg(target_arch = "x86_64")]
 #[cfg(target_os = "windows")]
 mod code_helper;
@@ -30,10 +31,15 @@ pub struct Page{
 }
 
 pub fn token_fail() ->axum::Json<JsonBack> {axum::Json(JsonBack{
-    errcode: 0,
+    errcode: 2000,
     errmsg: Some("鉴权失败: token无效".to_string())
 })}
 
+pub fn errcode0() ->axum::Json<JsonBack> {axum::Json(JsonBack{
+    errcode: 0,
+    errmsg: None
+})}
+
 #[allow(dead_code)]
 pub async fn example(axum::extract::State(_): axum::extract::State<crate::AppState>) -> axum::Json<JsonBack> {
     axum::Json(JsonBack{
@@ -42,11 +48,11 @@ pub async fn example(axum::extract::State(_): axum::extract::State<crate::AppSta
     })
 }
 
-pub async fn check_login(state: &crate::AppState, token: String) -> Result<i64,()>{
+pub async fn check_login(state: &crate::AppState, token: String) -> Result<u64,()>{
     state.db_lite.query(
         "select id from user where token=? and isdelete=0", 
         [token],  // 这里不能写作 rusqlite::params![token] 
-        |r|{r.get::<usize,i64>(0)}).await.map_err(|e| println!("{e}"))
+        |r|{r.get::<usize,u64>(0)}).await.map_err(|e| println!("{e}"))
 }
 
 pub fn token() -> String{

+ 14 - 4
src/api/user.rs

@@ -1,5 +1,6 @@
-use crate::{AppState, api::{JsonBack, DataBack, Page}, datasource::{Datasource,SqliteParams}};
-use crate::api::{check_login,token,md5,token_fail};
+use crate::{AppState, datasource::{Datasource,SqliteParams}};
+use super::{JsonBack, DataBack, Page};
+use super::{check_login,token,md5,token_fail};
 use serde::{Deserialize,Serialize};
 use axum::{Json, extract::State};
 
@@ -9,20 +10,27 @@ pub struct Uloggin{
     passwd: String,
 }
 
+#[derive(serde::Serialize)]
+pub struct Devices{
+    pub id: u64,
+    pub name: String,
+}
+
 #[derive(serde::Serialize)]
 pub struct UTokenBack{
     pub errcode: i16,
     #[serde(skip_serializing_if = "Option::is_none")]
     pub errmsg: Option<String>,
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub token: Option<String>
+    pub token: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub devices: Option<Vec<Devices>>
 }
 
 pub async fn u_loggin(
     State(state): State<AppState>,
     Json(u): axum::extract::Json<Uloggin>
 ) -> Json<UTokenBack> {
-
     let uid = match state.db_lite.query(
         "select id from user where uname=? and passwd=? and isdelete=0", 
         [u.uname,md5(u.passwd)],
@@ -36,6 +44,7 @@ pub async fn u_loggin(
                     errcode: 2000,
                     errmsg: Some(format!("登录失败")),
                     token: None,
+                    devices: None
                 })
             }
         };
@@ -47,6 +56,7 @@ pub async fn u_loggin(
         errcode: 0,
         errmsg: None,
         token: Some(token),
+        devices: None
     })
 }
 

+ 17 - 1
src/datasource/sqlite.rs

@@ -97,6 +97,22 @@ impl crate::datasource::Datasource for SqlitePool{
              Ok(Ok(conn)) => conn,
             Ok(Err(e)) => return Err(format!("connection err: {}",e.to_string())),
             Err(_) => return Err("Timeout".to_string())
-        }.execute(sql, params).map_err(|e| e.to_string())
+        }.execute(sql, params).map_err(|e| if e.sqlite_error_code()==Some(rusqlite::ErrorCode::ConstraintViolation) { println!("{e}");String::new() } else{ e.to_string()})
+    }
+}
+
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::datasource::Datasource;
+    #[tokio::test]
+    async fn test_sqlite_pool() {
+        let pool = init_sqlite_pool("./db.sqlite", 10).await.unwrap();
+
+        match pool.execute("insert into flow_task_share(did,typo,ticket)values(?,?,?)", (1,1,1)).await{
+            Ok(n) => println!("inserted {} rows", n),
+            Err(e) => println!("Err {}", e),
+        };
     }
 }

+ 3 - 3
src/main.rs

@@ -19,9 +19,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
             .route("/loggin", post(api::user::u_loggin))
             .route("/user/edit", post(api::user::u_edit))
             .route("/user/list", post(api::user::u_list))
-            // .route("/device/edit", post(api::user::d_edit))
-            // .route("/device/save", post(api::user::d_save))
-            // .route("/device/burn", post(api::user::d_save))
+            .route("/device/edit", post(api::device::d_edit))
+            .route("/flow/share/new", post(api::flow_task::new_flow_task_share_device))
+            .route("/flow/share/checkout", post(api::flow_task::checkout_flow_task_of_share_device))
         )
         // .route("/hello", get(api::example))
         .with_state(appstat)