Prechádzať zdrojové kódy

基本完整验证,带跳转

zii 1 mesiac pred
rodič
commit
e60306c3da
17 zmenil súbory, kde vykonal 1351 pridanie a 373 odobranie
  1. 3 0
      .cargo/config.toml
  2. 2 1
      .gitignore
  3. 416 220
      Cargo.lock
  4. 6 5
      Cargo.toml
  5. 64 55
      data_init.py
  6. 18 0
      docs/mosquitto.md
  7. 18 0
      docs/会议/2026-2-5.md
  8. 20 0
      docs/鉴权机制.md
  9. 16 0
      job.md
  10. 159 0
      src/api/area.rs
  11. 85 33
      src/api/auth.rs
  12. 165 11
      src/api/device.rs
  13. 81 27
      src/api/flow_task.rs
  14. 1 0
      src/api/mod.rs
  15. 72 2
      src/api/user.rs
  16. 45 4
      src/datasource/sqlite.rs
  17. 180 15
      src/main.rs

+ 3 - 0
.cargo/config.toml

@@ -1,3 +1,6 @@
 [target.armv7-unknown-linux-musleabihf]
 linker = "/cross/armv7l-linux-musleabihf-native/bin/armv7l-linux-musleabihf-gcc"
 rustflags = ["-C", "target-feature=+crt-static"]
+
+[target.stable-x86_64-pc-windows-msvc]
+rustflags = ["-C", "target-feature=+crt-static"]

+ 2 - 1
.gitignore

@@ -1,3 +1,4 @@
 /target
 *.sqlite
-*.db
+*.db
+*.json

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 416 - 220
Cargo.lock


+ 6 - 5
Cargo.toml

@@ -5,13 +5,14 @@ edition = "2024"
 
 [dependencies]
 rusqlite = { version = "0.37.0", features = ["bundled", "chrono"] }
-tokio = { version = "1.28.0", features = ["full"] }
-chrono = "0.4.42" 
+tokio = { version = "1.49.0", features = ["full"] }
+chrono = "0.4.43" 
 serde = { version = "1.0.228", features = ["derive"] }
-serde_json = "1.0.145"
-axum = { version = "0.8.7", features = ["macros"] }
+serde_json = "1.0.149"
+axum = { version = "0.8.8", features = ["macros"] }
 bb8 = "0.9.0"
 rand = "0.9.2"
 md5 = "0.8.0"
 tower-http = { version = "0.6", features = ["cors"] }
-reqwest = { version = "0.11", features = ["json"] }
+reqwest = { version = "0.13.1", features = ["json"] }
+axum-server={ version="0.8", features = ["tls-rustls"] }

+ 64 - 55
data_init.py

@@ -1,7 +1,7 @@
-import sqlite3
 # -----
 # 创建数据库
-curs = (conn:=sqlite3.connect('db.sqlite')).cursor()
+import sqlite3;curs = (conn:=sqlite3.connect('db.sqlite')).cursor()
+
 # --------
 # 表创建
 #
@@ -31,6 +31,10 @@ id integer PRIMARY KEY,
 name text not null,
 typo text not null,
 area integer not null default 0, -- 0:未分配到区域 其他:所在区域编号
+belongto integer not null default 0,
+info text not null default '{}',
+imgurl text not null default '',
+gramingurl text not null default '',
 isdelete integer not null default 0
 )
 ''').fetchall()
@@ -40,6 +44,7 @@ create table if not exists area (
 id integer PRIMARY KEY,
 name text not null,
 sup integer not null, -- 0:无上级区域 其他:上级区域编号
+createby integer not null,
 isdelete integer not null default 0
 )
 ''').fetchall()
@@ -53,14 +58,14 @@ unique(did,uid)
 )
 ''').fetchall()
 # 区域-设备映射表
-curs.execute('''
-create table if not exists map_area_device (
-id integer PRIMARY KEY,
-did integer not null,
-aid integer not null,
-unique(did,aid)
-)
-''').fetchall()
+# curs.execute('''
+# create table if not exists map_area_device (
+# id integer PRIMARY KEY,
+# did integer not null,
+# aid integer not null,
+# unique(did,aid)
+# )
+# ''').fetchall()
 # 用户-区域映射表
 curs.execute('''
 create table if not exists map_user_area (
@@ -74,12 +79,17 @@ curs.execute('''select * from user''').fetchall()
 # --------
 # 流程处理:分享与转移设备操作
 # 创建设备分享表
+curs.execute('''
+drop table if exists flow_task_share
+''').fetchall()
+
 curs.execute('''
 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, 因为分享不限用户
+createby integer not null,
 createtime timestamp not null default current_timestamp,
 isdelete integer not null default 0, -- 0:未处理 1:处理完毕 2:超时未处理
 unique(ticket)
@@ -91,50 +101,49 @@ conn.commit()
 # 表内容初始化
 #
 # 添加用户
-curs.execute('''
-insert into user (uname,passwd,nickname,openid) values 
-('root', 'e10adc3949ba59abbe56e057f20f883e', 'admin', 'XD'),
-('admin', 'e10adc3949ba59abbe56e057f20f883e', 'test', 'LD')
-''').fetchall()
-conn.commit()
+# curs.execute('''
+# insert into user (uname,passwd,nickname,openid) values 
+# ('root', 'e10adc3949ba59abbe56e057f20f883e', 'admin', 'XD'),
+# ('admin', 'e10adc3949ba59abbe56e057f20f883e', 'test', 'LD'),
+# ('testa', 'e10adc3949ba59abbe56e057f20f883e', 'testmana', ''),
+# ('testb', 'e10adc3949ba59abbe56e057f20f883e', 'testmanb', '')
+# ''').fetchall()
+# conn.commit()
 
-curs.execute('''
-select * from flow_task_share
-''').fetchall()
+# curs.execute('''
+# select * from flow_task_share
+# ''').fetchall()
 
-# 添加测试设备、区域、映射关系
-curs.execute('''
-insert into device (name,typo,area) values 
-('设备1', 'a',2),
-('设备2', 'a',4),
-('设备3', 'a',0)
-''').fetchall()
-curs.execute('''
-insert into area (name,sup) values 
-('区域1', 2),
-('区域2', 0),
-('区域3', 4),
-('区域4', 0)
-''').fetchall()
-curs.execute('''
-insert into map_user_device (uid,did) values 
-(1, 1),
-(1, 2),
-(1, 3),
-(1, 4),
-(2, 1),
-(2, 2),
-(2, 3),
-(2, 4)
-''').fetchall()
-curs.execute('''
-insert into map_user_area (uid,aid) values 
-(1, 1),
-(1, 2),
-(1, 3),
-(1, 4),
-(2, 1),
-(2, 2),
-(2, 3),
-(2, 4)
-''').fetchall()
+# # 添加测试设备、区域、映射关系
+# curs.execute('''
+# insert into device (name,typo,area,belongto) values 
+# ('设备1', 'a',1,1),
+# ('设备2', 'a',3,1),
+# ('设备3', 'a',0,1),
+# ('设备4', 'a',1,1)
+# ''').fetchall()
+# curs.execute('''
+# insert into area (name,sup,createby) values 
+# ('区域1', 2,1),
+# ('区域2', 0,1),
+# ('区域3', 4,1),
+# ('区域4', 0,1)
+# ''').fetchall()
+# curs.execute('''
+# insert into map_user_device (uid,did) values 
+# (1, 1),
+# (1, 2),
+# (1, 3),
+# (1, 4),
+# (2, 1)
+# ''').fetchall()
+# curs.execute('''
+# insert into map_user_area (uid,aid) values 
+# (1, 1),
+# (1, 2),
+# (1, 3),
+# (1, 4),
+# (2, 1)
+# ''').fetchall()
+
+# conn.commit()

+ 18 - 0
docs/mosquitto.md

@@ -0,0 +1,18 @@
+# mqtt broker 配置
+
+```conf
+
+# TCP listener
+listener 1884 0.0.0.0
+
+# WebSocket listener
+listener 9001 0.0.0.0
+protocol websockets
+
+# Allow anonymous connections
+allow_anonymous true
+
+# passwd
+password_file /etc/mosquitto/passwd
+
+```

+ 18 - 0
docs/会议/2026-2-5.md

@@ -0,0 +1,18 @@
+# 2026-2-5
+
+## 4G-PLC架构
+
+三台服务器: {官网服务器/前端服务器500M-ftp上传静态文件} | mqtt服务器 200M带宽(8元/G) 本地鉴权机制 | {api服务器/天翼云1核2G5M}
+
+## 登录方式
+
+ssh登录到容器,容器内部署
+ip:183.56.252.244
+端口:8022
+用户名/密码:root/worldflying2016
+
+ssh证书地址为服务器上的
+/etc/letsencrypt/live/plcplatform.worldflying.cn/fullchain.pem
+与
+/etc/letsencrypt/live/plcplatform.worldflying.cn/privkey.pem
+已经通过crontab添加了自动更新,直接调用即可。

+ 20 - 0
docs/鉴权机制.md

@@ -16,3 +16,23 @@ Gettoken  # 主要
 getjsapiticket
 Createlink
 Getcode
+
+```text
+1.微信公众号按钮会链接这个
+https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx520c15f417810387&redirect_uri=https%3A%2F%2Fchong.qq.com%2Fphp%2Findex.php%3Fd%3D%26c%3DwxAdapter%26m%3DmobileDeal%26showwxpaytitle%3D1%26vb2ctag%3D4_2030_5_1194_60&response_type=code&scope=snsapi_base&state=123#wechat_redirect
+2.用户会跳转回REDIRECT_URI?code
+因此REDIRECT_URI这个是https://aaabbb/api/fadfas/getcode
+后端代码提取/api/fadfas/getcode中的?后面内容,也就是code
+然后后端代码直接发送ajax往这个地址https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
+authorization_code是snsapi_userinfo或snsapi_base
+拿到了openid,判断这个用户是否之前登录过,如果没有,重新第一步,并让前端跳转到
+https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx807d86fb6b3d4fd2&redirect_uri=http%3A%2F%2Fdevelopers.weixin.qq.com&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
+方法是返回前端一个很小的页面,页面内容为直接跳转另一个url。
+进一步抓取微信公众号的昵称与头像
+
+页面与后台进行api请求,是直接发送openid吗?
+直接发送openid很危险,因此这里后台拿到openid后,随机生成一个32位随机数当做token,然后将token返回给前端。
+方法是{{杨星华的页面}}?token=ssxxxccd
+```
+
+设备自带二维码,扫码后配置设备/配网,此时可以绑定到用户,此时该用户“拥有/创建”该设备

+ 16 - 0
job.md

@@ -0,0 +1,16 @@
+# -
+
+##
+
+ssh root@183.56.252.244 -p 8022
+
+scp -P 8022 target/x86_64-unknown-linux-gnu/release/iotplatform_lite root@183.56.252.244:iotplatform_lite/
+
+scp -P 8022 ../../../add_service.sh root@183.56.252.244:iotplatform_lite/
+
+[https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx664469117500d259&redirect_uri=https%3A%2F%2Fwww.worldflying.cn%2Ftools%2Fwxredirect%2Fplc.html&response_type=code&scope=snsapi_base&state=base#wechat_redirect]
+
+
+https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx664469117500d259&redirect_uri=https%3A%2F%2Fwww.worldflying.cn%2Ftools%2Fwxredirect%2Fplc.html&response_type=code&scope=snsapi_base&state=base#wechat_redirect
+
+https://www.worldflying.cn/tools/wxredirect/iot.html?code=081C2G1w3lOms63CIf3w31t8n71C2G1j&state=base

+ 159 - 0
src/api/area.rs

@@ -0,0 +1,159 @@
+use axum::{Json, extract::State};
+
+use crate::{AppState, api::{JsonBack, check_token, errcode0, token_fail}, datasource::{Datasource, SqliteParams}};
+
+#[derive(serde::Deserialize)]
+pub struct ANew{
+    token: String,
+    name: String,
+    sup: u64
+}
+#[derive(serde::Serialize)]
+pub struct IdBack{
+    pub errcode: i16,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub errmsg: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub id: Option<i64>
+}
+pub async fn a_new(
+    State(state): State<AppState>,
+    Json(u): axum::extract::Json<ANew>
+) -> Json<IdBack> {
+    let uid=match check_token(&state, u.token).await {
+        Ok(id) => {id},
+        Err(_) => {
+            return Json(IdBack{
+                errcode: 2000,
+                errmsg: Some("鉴权失败: token无效".to_string()),
+                id: None,
+            });
+        }
+    };
+    match state.db_lite.execute(format!("insert into area (name,sup,createby)values(?,?,?)").as_str(), 
+    (u.name, u.sup, uid)).await{
+        Ok(_) => {},
+        Err(e) => {
+            return Json(IdBack{errcode: 3000, errmsg: Some(format!("更新失败{e}")),id:None});
+        }
+    }
+    let areaid =match state.db_lite.last_insert_rowid().await{
+            Ok(id) => id,
+            Err(_) => 0,
+        };
+    match state.db_lite.execute(format!("insert into map_user_area (aid,uid)values(?,?)").as_str(), 
+    (
+        areaid
+        , uid)).await{
+        Ok(_) => {},
+        Err(e) => {
+            return Json(IdBack{errcode: 3000, errmsg: Some(format!("更新失败{e}")),id:None});
+        }
+    }
+    Json(IdBack{
+    errcode: 0,
+    errmsg: None,
+    id:Some(areaid)
+})
+}
+
+#[derive(serde::Deserialize)]
+pub struct EditIdName{ 
+    token: String,
+    id: u64,
+    name: Option<String>,
+}
+
+pub async fn a_edit(
+    State(state): State<AppState>,
+    Json(u): axum::extract::Json<EditIdName>
+) -> Json<JsonBack> {
+    let uid = match check_token(&state, u.token).await {
+        Ok(id) => id,
+        Err(_) => {
+                        return token_fail();
+        }
+    };
+    if let Err(e) = state.db_lite.query(
+        "select a.id from map_user_area a where uid=? and aid=?", 
+        (uid,u.id), 
+        |r|{r.get::<usize,u64>(0)}).await{
+            return Json(JsonBack{errcode: 3000, errmsg: Some(
+                format!("编辑区域失败: {}",if e == ""{"区域不属于当前用户".to_string()}else{e})
+            )});
+    }
+
+    let mut params: SqliteParams = vec![];
+    match state.db_lite.execute(format!("update area set isdelete=0{} where id=?",
+        if let Some(filter) = u.name {params.push(Box::new(filter));", name=?"} else {""},
+    ).as_str(), rusqlite::params_from_iter({params.push(Box::new(u.id));params})).await{
+        Ok(_) => {},
+        Err(e) => {
+            return Json(JsonBack{errcode: 3000, errmsg: Some(format!("更新失败{e}"))});
+        }
+    }
+    errcode0()
+}
+
+
+pub async fn a_remove(
+    State(state): State<AppState>,
+    Json(a): axum::extract::Json<EditIdName>
+) -> Json<JsonBack> {
+    let uid=match check_token(&state, a.token).await {
+        Ok(id) => {id},
+        Err(_) => {
+            return token_fail();
+        }
+    };
+    match state.db_lite.execute("delete from map_user_area where (aid=? or aid in(select id from area where sup=?)) and uid=?", (a.id,a.id,uid)).await{
+        Ok(_) => {},
+        Err(e) => {
+            return Json(JsonBack{errcode: 3000, errmsg: Some(format!("删除区域关系失败: {e}"))});
+        }
+    }
+    match state.db_lite.execute("delete from map_user_device where uid=? and did in(select id from device where area in (select id from area where sup=? or id=?))", (uid,a.id,a.id)).await{
+        Ok(_) => {},
+        Err(e) => {
+            return Json(JsonBack{errcode: 3000, errmsg: Some(format!("删除区域关系失败: {e}"))});
+        }
+    }
+    errcode0()
+}
+pub async fn a_del(
+    State(state): State<AppState>,
+    Json(a): axum::extract::Json<EditIdName>
+) -> Json<JsonBack> {
+    let uid=match check_token(&state, a.token).await {
+        Ok(id) => {id},
+        Err(_) => {
+            return token_fail();
+        }
+    };
+    if let Err(e) = state.db_lite.query(
+        "select a.id from map_user_area a where a.uid=? and a.aid=?", 
+        (uid,a.id), 
+        |r|{r.get::<usize,u64>(0)}).await{
+            return Json(JsonBack{errcode: 3000, errmsg: Some(
+                format!("编辑区域失败: {}",if e == ""{"区域不属于当前用户".to_string()}else{e})
+            )});
+    }
+    if let Err(e) = state.db_lite.execute(
+        "update device set area=0 where area in (select id from area where sup=? or (id=? and createby=?))",
+        [a.id,a.id,uid]).await {
+        return Json(JsonBack{errcode: 3000, errmsg: Some(format!("删除设备失败: {e}"))});
+    }
+
+    if let Err(e) = state.db_lite.execute(
+        "update area set isdelete=1 where sup=? or(id=? and createby=?)",
+        [a.id,a.id,uid]).await {
+        return Json(JsonBack{errcode: 3000, errmsg: Some(format!("删除设备失败: {e}"))});
+    }
+    if let Err(e) = state.db_lite.execute(
+        "delete from map_user_area where aid=? and uid=?",
+        [a.id,uid]).await {
+        return Json(JsonBack{errcode: 3000, errmsg: Some(format!("删除设备失败: {e}"))});
+    }
+
+    errcode0()
+}

+ 85 - 33
src/api/auth.rs

@@ -1,10 +1,11 @@
-use axum::{extract::{Query, State}, http::HeaderMap, response::Redirect};
+use axum::{extract::{Query, State}, response::Redirect};
 
-use crate::{AppState, datasource::Datasource};
+use crate::{AppState, datasource::Datasource, log, LogLevel::*};
 use serde::Deserialize;
 
 const APPID: &str = "wx664469117500d259";
-const APPSECRET : &str = "wx664469117500d259";
+const APPSECRET : &str = "11ec6e6d4b9062f00a4fc08c529293be";
+// const APPSECRET : &str = "wx664469117500d259";
 
 #[derive(serde::Deserialize)]
 pub struct QueryParams {
@@ -19,13 +20,15 @@ pub struct WxGetToken{
 	expires_in: usize,
 	refresh_token: String,
 	openid: String,
-	scope:String,
-	url:String,
-	ticket: String,
-	errmsg: String,
-	errcode: usize
+	scope:String
 }
 
+/*
+https://open.weixin.qq.com/connect/oauth2/authorize?appid=
+&redirect_uri=http://192.168.24.101:3000/auth&response_type=code&scope=snsapi_base&state=1#wechat_redirect
+wx664469117500d259
+wxdf57fde70a442828
+ */
 
 #[allow(dead_code)]
 #[derive(Deserialize)]
@@ -37,7 +40,7 @@ pub struct WxUserInfo{
 }
 
 pub async fn auth(
-    headers: HeaderMap,
+    // headers: HeaderMap,
     State(state): State<AppState>,
     Query(q): Query<QueryParams>
 ) -> Redirect {
@@ -47,36 +50,82 @@ pub async fn auth(
     state为授权链接的state用于防止越权,用途不明
      */
     // 访问以获取微信token
-    let a:WxGetToken = if let Ok(a) = {if let Ok(a) = reqwest::get(format!(
+    log(Debug, format!("authing code+:`{}` state:`{}`",q.code,q.state));
+    // reqwest::Client::builder().build()?.get(url).send().await;
+    let webcli = match reqwest::Client::builder()
+        .timeout(std::time::Duration::from_secs(3))
+        .build()
+        {
+            Ok(cli) => cli,
+            Err(e) => {
+                log(Warning, format!("rewest client builder failed:: {e}"));
+                return Redirect::to(format!("{}/null",crate::WEBP).as_str())
+            }
+        };
+    log(Debug, format!("web client init"));
+    // webcli.get("url").send();
+    let a = match webcli.get(format!(
         "https://api.weixin.qq.com/sns/oauth2/access_token?appid={}&secret={}&code={}&grant_type=authorization_code",
-        APPID,
-        APPSECRET,
-        q.code
-    )).await{a.json().await} else {return Redirect::to("")}}{a} else {return Redirect::to("/")};
-
+        // "https://iotplatform.worldflying.cn/api/wx/auth?appid={}&secret={}&code={}&state={}&grant_type=authorization_code",
+            APPID,
+            APPSECRET,
+            q.code,
+            // q.state
+        )).send().await {
+            Ok(a) => {
+                // log(Info, format!("dealing"));
+                let v = a.text().await;
+                log(Debug, format!("dealing\n{:?}",v));
+                let v = if let Ok(v)=v {v}else {
+                    return Redirect::to(format!("{}/null",crate::WEBP).as_str());
+                };
+                
+                match
+                serde_json::from_str::<WxGetToken>(v.as_str())
+                //  a.json::<WxGetToken>().await
+                {
+                    Ok(b) => b,
+                    Err(e) =>{
+                        log(Error, format!("redirect 2 {e}, raw:{v}"));
+                        return Redirect::to(format!("{}/null",crate::WEBP).as_str())
+                    }
+                }
+            },
+            Err(e) => {
+                log(Debug, format!("redirect 0 {e}"));
+                return Redirect::to(format!("{}/null",crate::WEBP).as_str())
+            }
+        };
     // let host = headers.get("host")
     //     .and_then(|hv| hv.to_str().ok())
     //     .unwrap_or("localhost:3000");
-    let host = "192.168.24.102:5432";
-
+    log(Debuging, format!("in authing"));
     let token=super::token();
-    if let Ok(id) = super::check_openid(&state, a.openid.clone()).await{
+    let res = super::check_openid(&state, a.openid.clone()).await;
+    if let Ok(id) = res{
+        // 用户已经注册 函数末尾发生跳转到主页
         if let Err(e) = state.db_lite.execute("update user set token=? where id=?", (token.clone(), id)).await{
             println!("{e}");
         };
     } else if a.scope !="snsapi_userinfo"{
-        return Redirect::to(format!("https://open.weixin.qq.com/connect/oauth2/authorize?appid={}&redirect_uri=https://{}/api/auth&response_type=code&scope=snsapi_userinfo&state={}#wechat_redirect",
-        APPID,
-        host,
-        q.state
-    ).as_str())
+        // 
+        log(Debug, "send redirect for get uinfo".to_string());
+        return Redirect::to(
+            format!("https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx664469117500d259&redirect_uri=https%3A%2F%2Fwww.worldflying.cn%2Ftools%2Fwxredirect%2Fplc.html&response_type=code&scope=snsapi_userinfo&state={}#wechat_redirect",q.state).as_str()
+            // format!("https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx664469117500d259&redirect_uri=https%3A%2F%2Fwww.worldflying.cn%2Ftools%2Fwxredirect%2Fiot.html&response_type=code&scope=snsapi_userinfo&state={}#wechat_redirect",q.state).as_str()
+        )
     } else {
+        //
         let b:WxUserInfo = if let Ok(b) = {if let Ok(b) = reqwest::get(format!(
             "https://api.weixin.qq.com/sns/userinfo?access_token={}&openid={}&lang=zh_CN",
             a.access_token,
             a.openid.clone()
-        )).await{b.json().await} else {return Redirect::to("")}}{b} else {return Redirect::to("/")};
-        if let Err(e) = state.db_lite.execute("insert into user (nickname,headimgurl,openid,sex,token) values(?,?,?,?,?)", (b.nickname.clone(),b.headimgurl,b.openid,b.sex,token.clone())).await{
+        )).await{b.json().await} else {
+            log(Info, "redirect 4".to_string());
+            return Redirect::to(format!("{}/null",crate::WEBP).as_str())}}{b} else {
+            log(Info, "redirect 5".to_string());
+                return Redirect::to(format!("{}/null",crate::WEBP).as_str())};
+        if let Err(e) = state.db_lite.execute("insert into user (uname,nickname,passwd,headimgurl,openid,sex,token) values(?,?,?,?,?,?,?)", (b.nickname.clone(),b.nickname.clone(),"",b.headimgurl,b.openid,b.sex,token.clone())).await{
             println!("{e}");
         } 
         let uid = match state.db_lite.last_insert_rowid().await{
@@ -87,8 +136,8 @@ pub async fn auth(
             }
         };
         if let Err(e) = state.db_lite.execute(
-            "insert into area (name,sup)values(?,0)", 
-            [format!("{}的家",b.nickname.clone())]
+            "insert into area (name,sup,createby)values(?,0,?)", 
+            (format!("{}的家",b.nickname.clone()),uid)
         ).await{println!("{e}")};
         let area_sup: i64;
         if let Err(e) = state.db_lite.execute(
@@ -96,14 +145,17 @@ pub async fn auth(
             [uid,{area_sup=match state.db_lite.last_insert_rowid().await{Ok(id)=>id,Err(e)=>{println!("{e}");0}};area_sup}]
         ).await{println!("{e}")};
         if let Err(e) = state.db_lite.execute(
-            "insert into area (name,sup)values('客厅',?)", 
-            [area_sup]
+            "insert into area (name,sup,createby)values('客厅',?,?)", 
+            [area_sup,uid]
         ).await{println!("{e}")};
-        return Redirect::to(format!("https://{}/api/auth?token={}#{}",host,token.clone(),q.state).as_str());
+        log(Debug, "register success".to_string());
+        // 注册成功 函数末尾发生跳转到主页
+        // return Redirect::to(format!("http://124.222.106.170/plc_ctrl?token={}#{}",token.clone(),q.state).as_str());
     };
 
 
     // request::Request::get("body");
-    
-    Redirect::to("")
-}
+    // log(Info, format!("bad happend {:?}",res));
+    log(Debug, format!("redirect back with token"));
+    return Redirect::to(format!("{}?token={}#{}",crate::WEBP,token.clone(),q.state).as_str());
+}

+ 165 - 11
src/api/device.rs

@@ -1,9 +1,44 @@
-use crate::AppState;
+use crate::{AppState};
 use super::{JsonBack};
 use super::{token_fail,check_token, errcode0};
 use crate::datasource::{Datasource, SqliteParams};
 
 use axum::{Json,extract::State};
+use serde::Deserialize;
+
+// #[derive(serde::Deserialize)]
+// pub struct DTransform{ 
+//     token: String,
+//     id: i64,
+//     target: i64,
+//     keep: i8
+// }
+
+// pub async fn d_transform(
+//     State(state): State<AppState>,
+//     Json(d): axum::extract::Json<DTransform>
+// ) -> Json<JsonBack> {
+//     let uid = if let Ok(id)= check_token(&state, d.token).await{
+//         id
+//     } else{
+//         return token_fail();
+//     };
+//     if d.keep == 0 {
+//         if let Err(e) = state.db_lite.execute("delete from map_user_device where uid=? and did=?", (uid,d.id)).await{
+//             return Json(JsonBack{
+//                 errcode: 3000,
+//                 errmsg: Some(format!("移除原有关系失败: {}", e)),
+//             });
+//         };
+//     }
+//     if let Err(e) = state.db_lite.execute("insert into map_user_device (uid,did)values(?,?)", (uid,d.id)).await{
+//         return Json(JsonBack{
+//             errcode: 3000,
+//             errmsg: Some(format!("添加关系失败: {}", e)),
+//         });
+//     };
+//     errcode0()
+// }
 
 #[derive(serde::Deserialize)]
 pub struct Dedit{
@@ -11,45 +46,123 @@ pub struct Dedit{
     id: u64,
     name: Option<String>,
     area: Option<u64>,
+    info: Option<String>,
 }
 
 pub async fn d_edit(
     State(state): State<AppState>,
     Json(u): axum::extract::Json<Dedit>
 ) -> Json<JsonBack> {
-    match check_token(&state, u.token).await {
-        Ok(_) => {},
+    let uid =match check_token(&state, u.token).await {
+        Ok(id) => {id},
         Err(_) => {
             return token_fail();
         }
+    };
+    if let Err(e) = state.db_lite.query(
+        "select d.id from map_user_device d where d.uid=? and d.did=?", 
+        (uid,u.id), 
+        |r|{r.get::<usize,u64>(0)}).await{
+            return Json(JsonBack{errcode: 3000, errmsg: Some(
+                format!("编辑区域失败: {}",if e == ""{"区域不属于当前用户".to_string()}else{e})
+            )});
     }
-    let mut params: SqliteParams = vec![Box::new(0)];
-    match state.db_lite.execute(format!("update device set isdelete=?{}{} where id=?",
+
+    let mut params: SqliteParams = vec![];
+    match state.db_lite.execute(format!("update device set isdelete=0{}{}{} where id=?",
         if let Some(filter) = u.name {params.push(Box::new(filter));", name=?"} else {""},
         if let Some(filter) = u.area {params.push(Box::new(filter));", area=?"} else {""},
+        if let Some(filter) = u.info {params.push(Box::new(filter));", info=?"} else {""},
     ).as_str(), rusqlite::params_from_iter({params.push(Box::new(u.id));params})).await{
         Ok(_) => {},
+        Err(e) => {
+            return Json(JsonBack{errcode: 3000, errmsg: Some(format!("更新失败{e}"))});
+        }
+    }
+    errcode0()
+}
+
+
+pub async fn d_remove(
+    State(state): State<AppState>,
+    Json(u): axum::extract::Json<Dedit>
+) -> Json<JsonBack> {
+    let uid=match check_token(&state, u.token).await {
+        Ok(id) => {id},
         Err(_) => {
-            return Json(JsonBack{errcode: 3000, errmsg: Some("更新失败".to_string())});
+            return token_fail();
         }
+    };
+    if let Err(e) = state.db_lite.execute(
+        "update device set belongto=0 where id=? and belongto=?",
+        [u.id, uid]).await {
+        return Json(JsonBack{errcode: 3000, errmsg: Some(format!("更改设备从属失败: {e}"))});
     }
+    if let Err(e) = state.db_lite.execute(
+        "delete from map_user_device where did=? and uid=?",
+        [u.id, uid]).await {
+        return Json(JsonBack{errcode: 3000, errmsg: Some(format!("删除用户设备关系失败: {e}"))});
+    }
+
     errcode0()
 }
+pub async fn d_del(
+    State(state): State<AppState>,
+    Json(u): axum::extract::Json<Dedit>
+) -> Json<JsonBack> {
+    let uid=match check_token(&state, u.token).await {
+        Ok(id) => {id},
+        Err(_) => {
+            return token_fail();
+        }
+    };
+    if let Err(e) = state.db_lite.execute(
+        "update device set isdelete=1 where id=?",
+        [uid]).await {
+        return Json(JsonBack{errcode: 3000, errmsg: Some(format!("删除设备失败: {e}"))});
+    }
 
+    errcode0()
+}
+#[derive(Deserialize)]
+pub struct TokenPath{
+    token: String,
+    path: String
+}
+pub async fn burn(
+    State(state): State<AppState>,
+    Json(u): axum::extract::Json<TokenPath>
+) -> Json<JsonBack> {
+    let uid=match check_token(&state, u.token).await {
+        Ok(id) => {id},
+        Err(_) => {
+            return token_fail();
+        }
+    };
+    if let Err(e) = state.db_lite.execute(
+        "update device set burn=? where id=?",
+        (u.path,uid)).await {
+        return Json(JsonBack{errcode: 3000, errmsg: Some(format!("删除设备失败: {e}"))});
+    }
 
+    errcode0()
+}
 
 #[derive(serde::Serialize)]
 pub struct Devices{
     pub id: u64,
     pub name: String,
-    pub area: u64
+    pub area: u64,
+    pub belongto: bool,
+    pub info: String
 }
 
 #[derive(serde::Serialize)]
 pub struct Area{
     pub id: u64,
     pub name: String,
-    pub sup: u64
+    pub sup: u64,
+    createby: bool,
 }
 
 #[derive(serde::Serialize)]
@@ -63,6 +176,44 @@ pub struct UTokenBack{
     pub area: Option<Vec<Area>>
 }
 
+#[derive(serde::Deserialize)]
+pub struct DNew{
+    token: String,
+    name: String,
+    area: u64,
+    belongto: u64,
+    r#type: String,
+}
+
+// 添加设备的接口
+pub async fn d_new(
+    State(state): State<AppState>,
+    Json(u): axum::extract::Json<DNew>
+) -> Json<JsonBack> {
+     if let Ok( id) = check_token(&state, u.token).await {
+        id
+    } else {
+        return token_fail();
+    };
+    if let Err(e) = state.db_lite.execute(
+        "insert into device(name,area,belongto,typo)values(?,?,?,?)",
+        (u.name, u.area, u.belongto,u.r#type)).await {
+        return Json(JsonBack{errcode: 3000, errmsg: Some(format!("添加设备失败: {e}"))});
+    }
+    match state.db_lite.execute(format!("insert into map_user_device (did,uid)values(?,?)").as_str(), 
+    (
+        match state.db_lite.last_insert_rowid().await{
+            Ok(id) => id,
+            Err(_) => 0,
+        }
+        , u.belongto)).await{
+        Ok(_) => {},
+        Err(e) => {
+            return Json(JsonBack{errcode: 3000, errmsg: Some(format!("更新失败{e}"))});
+        }
+    }
+    errcode0()
+}
 
 pub async fn d_all(
     State(state): State<AppState>,
@@ -73,18 +224,20 @@ pub async fn d_all(
     } else {
         return Json(UTokenBack{errcode: 2000, errmsg: Some("鉴权失败: token无效".to_string()),devices: None,area: None})
     };
-    
+    // 区域是否为自己创建的
     Json(UTokenBack{
         errcode: 0,
         errmsg: None,
         devices: Some(
             match state.db_lite.query_rows(
-                "select d.id,d.name,d.area from device d left join map_user_device m on d.id=m.did where d.isdelete=0 and m.uid=?", 
+                "select d.id,d.name,d.area,d.belongto,d.info from device d left join map_user_device m on d.id=m.did where d.isdelete=0 and m.uid=?", 
                 [uid], 
                 |r|{Ok(Devices{
                 id: r.get::<usize,u64>(0)?,
                 name: r.get::<usize,String>(1)?,
                 area: r.get::<usize,u64>(2)?,
+                belongto: r.get::<usize,u64>(3)?==uid,
+                info:  r.get::<usize,String>(4)?
             })}).await{
                 Ok(ans) => ans,
                 Err(e) => {
@@ -100,12 +253,13 @@ pub async fn d_all(
             ),
             area: Some(
             match state.db_lite.query_rows(
-                "select a.id,a.name,a.sup from area a left join map_user_area m on a.id=m.aid where a.isdelete=0 and m.uid=?", 
+                "select a.id,a.name,a.sup,a.createby from area a left join map_user_area m on a.id=m.aid where a.isdelete=0 and m.uid=?", 
                 [uid], 
                 |r|{Ok(Area{
                 id: r.get::<usize,u64>(0)?,
                 name: r.get::<usize,String>(1)?,
                 sup: r.get::<usize,u64>(2)?,
+                createby: r.get::<usize,u64>(3)?==uid,
             })}).await{
                 Ok(ans) => ans,
                 Err(e) => {

+ 81 - 27
src/api/flow_task.rs

@@ -1,10 +1,10 @@
-use crate::AppState;
+use crate::{AppState,Debug, log};
 use super::{JsonBack, errcode0, token};
 use super::{token_fail,check_token};
 use crate::datasource::Datasource;
 
 use serde::{Serialize, Deserialize};
-use axum::{Json,extract::{State, Query}, http::HeaderMap};
+use axum::{Json,extract::{State}, http::HeaderMap};
 
 
 #[derive(Deserialize)]
@@ -19,22 +19,30 @@ pub struct UrlBack{
     errmsg: Option<String>,
     url: Option<String>,
 }
+
+#[allow(unused_variables)]
 pub async fn new_flow_task_share_device(
     headers: HeaderMap,
     State(state): State<AppState>,
     Json(u): axum::extract::Json<TaskOfDeviceShareOrTransfer>
 ) -> Json<UrlBack> {
-    match check_token(&state, u.token).await {
-        Ok(_) => {},
+    let uid=match check_token(&state, u.token).await {
+        Ok(id) => {id},
         Err(_) => {
             return Json(UrlBack{errcode: 3000, errmsg: Some(format!("鉴权失败: token失效")),url:None});
         }
+    };
+    match u.r#type{
+        0|1|2 => {},
+        _ => {
+            return Json(UrlBack{errcode: 3000, errmsg: Some(format!("参数错误: type应为0、1、2")),url:None});
+        }
     }
     let mut tryno=0;
-    let ticket = token();
+    let linkrand = 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{
+        "insert into flow_task_share (did, typo, ticket,createby)values (?,?,?,?)", 
+        (u.id,u.r#type,linkrand.clone(),uid)).await{
         Ok(_) => false,
         Err(e) => 
             if e==""{true}  // execute时如果遇到Unique异常时会将异常处理为空字符串
@@ -48,23 +56,29 @@ pub async fn new_flow_task_share_device(
     }
     
     // 获取主机名
-    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}"))})
+    // let host = format!("https://{}",headers.get("host")
+    //     .and_then(|hv| hv.to_str().ok())
+    //     .unwrap_or("localhost:3000"));
+    let host = crate::WEBP;
+    Json(UrlBack{errcode: 0, errmsg: None,url:Some(format!("{host}?linkrand={linkrand}"))})
 }
 
 
+// #[derive(Deserialize)]
+// pub struct QueryParams {
+//     linkrand: String,
+// }
+
 #[derive(Deserialize)]
-pub struct QueryParams {
-    ticket: String,
+pub struct CheckoutShare {
+token: String,
+    linkrand: String,
 }
 
 pub async fn checkout_flow_task_of_share_device(
     State(state): State<AppState>,
-    Query(params): Query<QueryParams>,
-    Json(u): Json<super::Ident>
+    // Query(params): Query<QueryParams>,
+    Json(u): Json<CheckoutShare>
 ) -> Json<JsonBack> {
     let uid = match check_token(&state, u.token).await {
         Ok(id) => id,
@@ -72,9 +86,12 @@ pub async fn checkout_flow_task_of_share_device(
             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{
+    let (id, typo,interval, createby) = match state.db_lite.query(
+        "select did,typo,strftime('%s','now')-strftime('%s',createtime),createby from flow_task_share where ticket=? and isdelete=0",
+        [
+            // params.linkrand.clone()
+            u.linkrand.clone()
+        ], |r|Ok((r.get::<usize, u64>(0)?,r.get::<usize, u8>(1)?,r.get::<usize, u64>(2)?,r.get::<usize, u64>(3)?))).await{
         Ok(a) => a,
         Err(_) => return Json(JsonBack{errcode: 3000, errmsg: Some(
             "未找到分享流程".to_string()
@@ -85,29 +102,66 @@ pub async fn checkout_flow_task_of_share_device(
         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{
+            [u.linkrand.clone()]).await{
             Ok(_) => {},
             Err(e) => return Json(JsonBack{errcode: 3001, errmsg: Some(format!("删除分享流程失败: {e}"))}),
         };
         return Json(JsonBack{errcode: 3001, errmsg: Some("分享流程已过期".to_string())});
     }
+    // 为2时转让设备,需要移除原用户与设备的关系
+    if typo == 2{
+        if let Err(e) = state.db_lite.execute("delete from map_user_device where uid=? and did=?", (createby, id)).await{
+            return Json(JsonBack{errcode: 3001, errmsg: Some(format!("转让时删除设备关系失败: {e}"))});
+        }
+    }
+
+    // 为0时分享区域
     if typo == 0{
         match state.db_lite.execute("insert into map_user_area(uid,aid)values(?,?)", [uid, id]).await{
-            Ok(_) => {
+            Ok(i) => {
+                log(Debug, format!("影响一级区域数量{i}个"));
+            },
+            Err(e) => if e==""{/*Unique异常跳过 */} else {return Json(JsonBack{errcode: 3002, errmsg: Some(format!("添加区域分享关系失败: {e}"))});},
+        };
+        match state.db_lite.execute("insert into map_user_area(uid,aid) select ?,id from area where sup=? and id not in (select aid from map_user_area where uid=?)", [uid, id,uid]).await{
+            Ok(i) => {
+                log(Debug, format!("影响二级区域数量{i}个"));
+            },
+            Err(e) =>  if e==""{/*Unique异常跳过 */} else {return Json(JsonBack{errcode: 3002, errmsg: Some(format!("添加区域下级分享关系失败: {e}"))})},
+        };
+        match state.db_lite.execute("insert into map_user_device(uid,did) select ?,device.id from device where area in (select id from area where area.sup=?) and device.id not in (select did from map_user_device where uid=?)", [uid, id,uid]).await{
+            Ok(i) => {
+                log(Debug, format!("影响设备数量{i}个"));
             },
-            Err(e) => return Json(JsonBack{errcode: 3002, errmsg: Some(format!("添加设备分享关系失败: {e}"))}),
+            Err(e) =>if e==""{/*Unique异常跳过 */} else { return Json(JsonBack{errcode: 3002, errmsg: Some(format!("添加区域下全部设备关系失败: {e}"))})},
         };
-    }else if typo == 1 {
+
+    }
+    // 为1时分享设备,为2时转让设备,此处添加设备与新用户的关系
+    else if typo == 1 || typo == 2 {
         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}"))}),
+            Err(e) => if e==""{/*Unique异常跳过*/}else{ 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{
+
+    // 转让设备需要将belongto更新
+    if typo == 2{
+        match state.db_lite.execute("update device set belongto=? where id=?", [uid, id]).await{
+            Ok(_) => {},
+            Err(e) => return Json(JsonBack{errcode: 3004, errmsg: Some(format!("更新设备分享关系失败: {e}"))}),
+        }
+        match state.db_lite.execute("delete from flow_task_share where ticket=?", [u.linkrand.clone()]).await{
+            Ok(_) => {},
+            Err(e) => {println!("when delete transform flow got error: {e}")}
+        }
+    } else {
+
+    match state.db_lite.execute("delete from flow_task_share where strftime('%s','now')-strftime('%s',createtime)>30*60", []).await{
         Ok(_) => {},
-        Err(_) => {}
-    }
+        Err(e) => {println!("when delete extra flow got error: {e}")}
+    }}
     errcode0()
 }

+ 1 - 0
src/api/mod.rs

@@ -1,4 +1,5 @@
 pub mod user;
+pub mod area;
 pub mod auth;
 pub mod device;
 pub mod flow_task;

+ 72 - 2
src/api/user.rs

@@ -1,4 +1,4 @@
-use crate::{AppState, datasource::{Datasource,SqliteParams}};
+use crate::{AppState, datasource::{Datasource,SqliteParams}, log};
 use super::{JsonBack, DataBack, Page};
 use super::{check_token,token,md5,token_fail};
 use serde::{Deserialize,Serialize};
@@ -45,6 +45,15 @@ pub async fn u_loggin(
     State(state): State<AppState>,
     Json(u): axum::extract::Json<Uloggin>
 ) -> Json<UTokenBack> {
+    if u.passwd == ""{
+        return Json(UTokenBack{
+            errcode: 2000,
+            errmsg: Some(format!("用户名或密码错误")),
+            token: None,
+            devices: None,
+            area: None
+        })
+    }
     let uid = match state.db_lite.query(
         "select id from user where uname=? and passwd=? and isdelete=0", 
         [u.uname,md5(u.passwd)],
@@ -105,7 +114,7 @@ pub async fn u_loggin(
             })}).await{
                 Ok(ans) => ans,
                 Err(e) => {
-                    println!("{e}");
+                    log(crate::LogLevel::Warning,format!("{e}"));
                     return Json(UTokenBack{
                         errcode: 3000,
                         errmsg: Some(format!("设备信息获取失败")),
@@ -236,4 +245,65 @@ pub async fn u_list(
                 })
             }
         }
+}
+
+
+// #[derive(Serialize)]
+// pub struct Jsconfig{
+//     debug: bool,
+//     #[serde(rename = "appId")]
+//     appid: String,
+//     timestamp: u64,
+// }
+
+// pub async fn getjsconfig(
+//     State(state): State<AppState>,
+//     Json(u): axum::extract::Json<WxGetToken>
+// ) -> Json<DataBack<Jsconfig>>{
+
+//     return Json(DataBack{
+//         errcode: 3000,
+//         errmsg: Some("".to_string()),
+//         data: None
+// });
+// }
+#[derive(Serialize)]
+pub struct UInfo{
+    errcode: u32,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    errmsg: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    nickname: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    headimgurl: Option<String>
+}
+
+pub async fn u_info(
+    State(state): State<AppState>,
+    Json(u): axum::extract::Json<super::Ident>
+) -> Json<UInfo>{
+    let uid = match check_token(&state, u.token).await{
+        Ok(uid) => uid,
+        Err(_) => return Json(UInfo { errcode: 2000, errmsg: Some("鉴权失败: token失效".to_string()), nickname: None, headimgurl: None })
+    };
+    match state.db_lite.query("select nickname, headimgurl from user where id=?", [uid], |r| {
+        Ok(
+            UInfo{
+                errcode: 0,
+                errmsg: None,
+                nickname: Some(r.get(0)?),
+                headimgurl: Some(r.get(1)?)
+            }
+        )
+    }).await {
+        Ok(ans) => Json(ans),
+        Err(e) => {
+            Json(UInfo{
+                errcode: 3000,
+                errmsg: Some(format!("用户信息获取失败: {e}")),
+                nickname: None,
+                headimgurl: None
+            })
+        }
+    }
 }

+ 45 - 4
src/datasource/sqlite.rs

@@ -1,5 +1,7 @@
-
 use bb8::Pool;
+
+use crate::{Debug, Warning, log};
+
 /// SQLite连接池类型
 pub type SqlitePool = bb8::Pool<SqliteConnectionManager>;
 /// SQLite连接管理器
@@ -74,7 +76,7 @@ impl crate::datasource::Datasource for SqlitePool{
             Ok(Err(e)) => return Err(format!("connection err: {}",e.to_string())),
             Err(_) => return Err("Timeout".to_string())
         }.prepare(sql)
-        .map_err(|e| e.to_string())
+        .map_err(|e| {log(Warning, format!("{}:{}:{}", file!(), line!(), column!()));e.to_string()})
         .and_then(|mut stmt| {
             let mut results = Vec::new();
             let mut rows = stmt.query(params).map_err(|e| e.to_string())?;
@@ -97,7 +99,7 @@ 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| if e.sqlite_error_code()==Some(rusqlite::ErrorCode::ConstraintViolation) { println!("{e}");String::new() } else{ e.to_string()})
+        }.execute(sql, params).map_err(|e| {log(Debug, format!("sqlerr: {sql} {e}"));if e.sqlite_error_code()==Some(rusqlite::ErrorCode::ConstraintViolation) { String::new() } else{ e.to_string()}})
     }
     async fn last_insert_rowid(&self) -> Result<i64, String> {
         Ok(match tokio::time::timeout(std::time::Duration::from_secs(5), self.get()).await {
@@ -108,11 +110,45 @@ impl crate::datasource::Datasource for SqlitePool{
     }
 }
 
-
 #[cfg(test)]
 mod tests {
     use super::*;
     use crate::datasource::Datasource;
+    
+    /* 获取 调用者被调用位置 的marco形式实现 */
+    #[macro_export]
+    macro_rules! supper {
+        () => {{
+            // 由于在宏中使用backtrace::resolve_frame存在生命周期问题,我们使用更简单但有效的方法
+            // Rust的宏在编译时展开,所以宏中的位置信息就是调用宏的位置
+            let location = std::panic::Location::caller();
+            
+            format!("called from {}:{}:{}", location.file(), location.line(), location.column())
+            // format!("called from {}:{}:{}", file!(), line!(), column!())
+        }};
+    }
+
+    /* 获取 调用者被调用位置 的函数形式实现 */
+    #[track_caller]
+    fn suppper(){
+        let location = std::panic::Location::caller();
+        println!("Called from {}:{}:{}", location.file(), location.line(), location.column());
+    }
+    
+    // 测试函数
+    fn test_suupp_with_macro() {
+        /* 在这里调用用来获取 调用者被调用位置 的实现 */
+        println!("Using macro: {}", supper!());
+        suppper();
+    }
+    
+    // 新增测试用例,演示宏如何获取调用位置
+    #[test]
+    fn test_supper_macro() {
+        
+        /* 应当打印此处的行号与文件名 */ test_suupp_with_macro();
+    }
+
     #[tokio::test]
     async fn test_sqlite_pool() {
         let pool = init_sqlite_pool("./db.sqlite", 10).await.unwrap();
@@ -121,5 +157,10 @@ mod tests {
             Ok(n) => println!("inserted {} rows", n),
             Err(e) => println!("Err {}", e),
         };
+
+        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),
+        };
     }
 }

+ 180 - 15
src/main.rs

@@ -1,33 +1,156 @@
 mod datasource;
 mod api;
+use std::{collections::HashMap, process::exit, str::FromStr};
+
+use axum::{extract::{Request, State}, middleware::Next, routing::any};
 use datasource::sqlite;
+use tokio::sync::Mutex;
 use tower_http::cors::{Any, CorsLayer}; // 添加Any和CorsLayer的引用
 
+const WEBP: &str =
+ "https://www.worldflying.cn/tools/4gplc/"
+// "http://124.222.106.170/plc_ctrl"
+;
+
+
+fn now() -> String{
+    chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string()
+}
+
+
+#[repr(u8)]
+#[derive(PartialEq, PartialOrd,Debug)]
+#[allow(dead_code)]
+pub enum LogLevel{
+    Debug = 0,
+    Warning,
+    Info,
+    Error,
+    OFF = 0xfe,
+    Debuging,
+}
+
+use LogLevel::*;
+const LOG_LEVEL: LogLevel = Debug;
+
+pub fn log(level: LogLevel, msg: String) {
+    if level < LOG_LEVEL {
+        return;
+    }
+    println!("[{:?}] {} {}", level, now(), msg);
+}
+
 #[derive(Clone)]
 struct AppState{
     db_lite: sqlite::SqlitePool
 }
 
+#[derive(Clone)]
+struct Counter {
+    counts: std::sync::Arc<Mutex<HashMap<String, [u64;2]>>>,
+}
+
+impl Counter {
+    fn new() -> Self {
+        Counter {
+            counts: std::sync::Arc::new(Mutex::new(HashMap::new())),
+        }
+    }
+    async fn increment(&self, path: &str, ok: bool) {
+        let mut counts = self.counts.lock().await;
+        (*counts.entry(path.to_string()).or_insert([0,0]))[if ok{0}else{1}] +=1;
+    }
+    async fn get_counts(&self) -> HashMap<String, [u64;2]> {
+        let counts = self.counts.lock().await;
+        HashMap::from(counts.clone())
+    }
+    // async fn clean(&self) {
+    //     let mut counts = self.counts.lock().await;
+    //     counts.clear();
+    // }
+}
+// cargo run --package iotplatform_lite --bin iotplatform_lite 
+const CFGPATH: &'static str = "config.json";
+
 #[tokio::main]
 async fn main() -> Result<(), Box<dyn std::error::Error>> {
-    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
+#[derive(serde::Deserialize,serde::Serialize, Debug)]
+    pub struct Conf{
+        host: String,
+        ssl: Option<String>,
+        db: String,
+        ssl_cert: Option<String>,
+        ssl_key: Option<String>,
+    }
+    let conf:Conf =match serde_json::from_reader(match std::fs::File::open(CFGPATH){
+        Ok(f) => f,
+        Err(_) => {let f=std::fs::File::create_new(CFGPATH).unwrap();
+            serde_json::to_writer(&f, &Conf{
+                ssl: None,
+                host: "0.0.0.0:3000".to_string(),
+                db: "db.sqlite".to_string(), 
+                ssl_cert: None,
+                ssl_key: None,
+        }).unwrap();
+            f}
+    }){
+        Ok(conf) => conf,
+        Err(_) => {log(Warning, "config file phrase fail, overwriting ...\n\t\tdone, running with default configuration".to_string());Conf { host: "0.0.0.0:3000".to_string(), db: "db.sqlite".to_string(),ssl_cert:None, ssl_key:None,ssl:None }},
+    };
 
-    let appstat = AppState{db_lite: sqlite::init_sqlite_pool("./db.sqlite", 10).await?};
+    let appstat = AppState{db_lite: sqlite::init_sqlite_pool(&conf.db, 10).await?};
+
+    let monitor = Counter::new();
 
     use axum::routing::{post,get};
     let app =  axum::Router::new()
+        .route("/api/wx/auth", get(api::auth::auth))
+        .with_state(appstat.clone())
          // 应用完整的CORS配置
-        .nest("/api", axum::Router::new()
+        .nest("/apilite", axum::Router::new()
+        // .nest("/api", axum::Router::new()
+            // .route("/wx/auth", get(api::auth::auth))
+    
             .route("/loggin", post(api::user::u_loggin))
+
+            .route("/device", post(api::device::d_all))
+
+            .route("/user/info", post(api::user::u_info))
             .route("/user/edit", post(api::user::u_edit))
             .route("/user/list", post(api::user::u_list))
-            .route("/device", post(api::device::d_all))
 
+            // .route("/user/getjsconfig", post(api::user::getjsconfig))
+            
+            .route("/area/new", post(api::area::a_new))
+            .route("/area/edit", post(api::area::a_edit))
+            .route("/area/delete", post(api::area::a_del))
+            .route("/area/remove", post(api::area::a_remove))
+            
             .route("/device/edit", post(api::device::d_edit))
+            // .route("/device/transform", post(api::device::d_transform))
+            .route("/device/new", post(api::device::d_new))
+            .route("/device/delete", post(api::device::d_del))
+            .route("/device/remove", post(api::device::d_remove))
+
+            .route("/device/burn", post(api::device::burn))
+            // .route("/device/save", post(todo!()))
+
             .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("/auth", get(api::auth::auth))
+            .route_layer(
+                axum::middleware::from_fn_with_state(
+                    monitor.clone(), 
+                    |a:State<Counter>,req: Request,c:Next | async move{
+                        log(Debuging, format!("in url {} {:?}",req.uri().path(),req.uri().query()));
+                        let url = format!("{} {}",req.method().as_str(),req.uri().path());
+                        let resp = c.run(req).await;
+                        a.increment(url.as_str(),resp.status().is_success()).await;
+                        resp
+                    }
+                )
+            )
+            .with_state(appstat)
 
             // layer需要在最后添加,添加视为入栈,生效顺序为出栈顺序
             .layer(CorsLayer::new()
@@ -35,17 +158,59 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
                 .allow_methods(Any)
                 .allow_headers(Any))
         )
-        .route("/", get(|| async {"hello".to_string()}))
-        .route("/", post(|| async {"hello".to_string()}))
+        // .route("/", get(|couter:State<Counter>| async move {"hello".to_string()}))
+        .route("/", any(|| async {"hello".to_string()}))
         // .route("/hello", get(api::example))
-        
-        .with_state(appstat)
-
-        .layer(CorsLayer::new()
-            .allow_origin(Any)
-            .allow_methods(Any)
-            .allow_headers(Any))
+        .nest("/monitor", axum::Router::new()
+            .route("/show", get(|c:State<Counter>|async move{ axum::Json::from(c.get_counts().await)  }))
+            .with_state(monitor)
+        )
     ;
-    axum::serve(listener, app).await?;
+    if let (Some(cert),Some(key),Some(ssl)) = (conf.ssl_cert,conf.ssl_key,conf.ssl) {
+        use axum_server::tls_rustls::RustlsConfig;
+        use std::{path::PathBuf,net::SocketAddr};
+        log(Info, format!("rederect for http2https at {}==>{}",conf.host,ssl));
+        let (listener, app2) = (
+            tokio::net::TcpListener::bind(conf.host).await?, 
+            axum::Router::new()
+            .route("/", any(redirect_http_to_https)));
+        tokio::spawn(async move{
+            log(Info, format!("http2https start"));
+            axum::serve(listener,app2).await
+        });
+
+        log(Info, format!("host https at {}",ssl));
+        log(Info, "starting with ssl-https".to_string());
+        axum_server::bind_rustls(
+            match SocketAddr::from_str(&ssl){
+                Ok(a) => {log(Debug, format!("ssl starting in {a}"));a},
+                Err(e) => {log(Error, format!("{e}"));exit(1)}
+            }, 
+            match RustlsConfig::from_pem_file(
+                PathBuf::from(cert),
+                PathBuf::from(key),
+            ).await{
+                Ok(k) => k,
+                Err(e) => {log(Error, format!("{e}"));exit(1)}
+            }
+        ).serve(app.into_make_service()).await?;
+    } else {
+        log(Info, format!("hosting at {}",conf.host));
+        let listener = tokio::net::TcpListener::bind(conf.host).await?;
+        axum::serve(listener, app).await?;    
+    }
+    log(Info, format!("bye!"));
     Ok(())
+}
+
+
+use axum::{response::Redirect, http::{HeaderMap,Uri,StatusCode}};
+async fn redirect_http_to_https(host:HeaderMap, uri: Uri) -> Result<Redirect, StatusCode> {
+    if let Some(host) = host.get("Host").and_then(|t|{t.to_str().ok()}){
+    let https_uri = format!("https://{}{}", host, uri.path());
+    log(Debuging, format!("asdf WTF!! {https_uri}"));
+    Ok(Redirect::permanent(&https_uri))
+    } else {
+        Err(StatusCode::BAD_REQUEST)
+    }
 }

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov