rbatis db

master
git 2 years ago
parent 5cf66de8ff
commit 198fec5c19

173
oab/Cargo.lock generated

@ -187,6 +187,41 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aead"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877"
dependencies = [
"generic-array 0.14.5",
]
[[package]]
name = "aes"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
"opaque-debug 0.3.0",
]
[[package]]
name = "aes-gcm"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6"
dependencies = [
"aead",
"aes",
"cipher",
"ctr",
"ghash",
"subtle",
]
[[package]]
name = "ahash"
version = "0.7.6"
@ -298,7 +333,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1e50562e37200edf7c6c43e54a08e64a5553bfb59d9c297d5572512aa517256"
dependencies = [
"num-bigint",
"num-bigint 0.3.3",
"num-integer",
"num-traits",
"serde",
@ -322,7 +357,7 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
dependencies = [
"block-padding",
"block-padding 0.1.5",
"byte-tools",
"byteorder",
"generic-array 0.12.4",
@ -346,6 +381,15 @@ dependencies = [
"byte-tools",
]
[[package]]
name = "block-padding"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a90ec2df9600c28a01c56c4784c9207a96d2451833aeceb8cc97e4c9548bb78"
dependencies = [
"generic-array 0.14.5",
]
[[package]]
name = "brotli"
version = "3.3.4"
@ -429,6 +473,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "cipher"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
dependencies = [
"generic-array 0.14.5",
]
[[package]]
name = "clap"
version = "3.1.18"
@ -570,6 +623,15 @@ dependencies = [
"typenum",
]
[[package]]
name = "ctr"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea"
dependencies = [
"cipher",
]
[[package]]
name = "dashmap"
version = "4.0.2"
@ -821,6 +883,16 @@ dependencies = [
"wasi 0.10.2+wasi-snapshot-preview1",
]
[[package]]
name = "ghash"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99"
dependencies = [
"opaque-debug 0.3.0",
"polyval",
]
[[package]]
name = "h2"
version = "0.3.13"
@ -1007,6 +1079,20 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "jsonwebtoken"
version = "8.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aa4b4af834c6cfd35d8763d359661b90f2e45d8f750a0849156c7f4671af09c"
dependencies = [
"base64",
"pem",
"ring",
"serde",
"serde_json",
"simple_asn1",
]
[[package]]
name = "language-tags"
version = "0.3.2"
@ -1149,6 +1235,17 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-bigint-dig"
version = "0.8.1"
@ -1221,16 +1318,24 @@ name = "oab"
version = "0.1.0"
dependencies = [
"actix-web",
"aes-gcm",
"base64",
"block-padding 0.3.2",
"chrono",
"clap",
"futures-util",
"generic-array 0.14.5",
"include_dir",
"jsonwebtoken",
"lazy_static",
"rand",
"rbatis",
"rbson",
"serde",
"serde-big-array",
"serde_json",
"serde_yaml",
"thiserror",
"time 0.3.9",
"tokio",
"tracing",
"tracing-subscriber",
@ -1248,6 +1353,12 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "os_str_bytes"
version = "6.1.0"
@ -1308,6 +1419,15 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc"
[[package]]
name = "pem"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9a3b09a20e374558580a4914d3b7d89bd61b954a5a5e1dcbea98753addb1947"
dependencies = [
"base64",
]
[[package]]
name = "pem-rfc7468"
version = "0.3.1"
@ -1400,6 +1520,18 @@ dependencies = [
"zeroize",
]
[[package]]
name = "polyval"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1"
dependencies = [
"cfg-if",
"cpufeatures",
"opaque-debug 0.3.0",
"universal-hash",
]
[[package]]
name = "ppv-lite86"
version = "0.2.16"
@ -1742,6 +1874,15 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "serde-big-array"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3323f09a748af288c3dc2474ea6803ee81f118321775bffa3ac8f7e65c5e90e7"
dependencies = [
"serde",
]
[[package]]
name = "serde_bytes"
version = "0.11.6"
@ -1807,7 +1948,7 @@ dependencies = [
"block-buffer 0.7.3",
"digest 0.8.1",
"fake-simd",
"opaque-debug",
"opaque-debug 0.2.3",
]
[[package]]
@ -1876,6 +2017,18 @@ dependencies = [
"libc",
]
[[package]]
name = "simple_asn1"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085"
dependencies = [
"num-bigint 0.4.3",
"num-traits",
"thiserror",
"time 0.3.9",
]
[[package]]
name = "slab"
version = "0.4.6"
@ -1956,7 +2109,7 @@ dependencies = [
"libc",
"log",
"memchr",
"num-bigint",
"num-bigint 0.3.3",
"once_cell",
"paste",
"percent-encoding",
@ -2373,6 +2526,16 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "universal-hash"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05"
dependencies = [
"generic-array 0.14.5",
"subtle",
]
[[package]]
name = "untrusted"
version = "0.7.1"

@ -12,8 +12,9 @@ serde = { version = "1", features = ["derive"] }
serde_json = "*"
serde_yaml = "*"
clap = { version = "3", features = ["derive"] }
time = { version = "0.3", features = ["formatting", "macros"] }
chrono = "0.4"
tokio = { version = "1", features = ["full"] }
futures-util = "*"
tracing = "*"
tracing-subscriber = "*"
thiserror = "1.0"
@ -21,4 +22,11 @@ thiserror = "1.0"
rbson = "2"
rbatis = { version = "*", default-features = false, features = ["runtime-tokio-rustls","mysql","debug_mode"] }
actix-web = "4"
jsonwebtoken = "8"
aes-gcm="0.9"
rand = "0.8.5"
block-padding = "0.3.2"
generic-array = "0.14.5"
serde-big-array = "0.4.1"
base64 = "0.13.0"

@ -0,0 +1,11 @@
#
# Makefile
# Copyright (C) 2022 veypi <i@veypi.com>
# 2022-07-16 23:43
# Distributed under terms of the Apache license.
#
run:
@cargo run
init:
@cargo run -- init

@ -4,22 +4,27 @@
*
* Distributed under terms of the Apache license.
*/
DROP DATABASE test;
CREATE DATABASE test CHARSET=utf8;
USE test;
CREATE TABLE IF NOT EXISTS `user`
(
`id` varchar(32) NOT NULL DEFAULT '' COMMENT 'User UUID',
`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`delete_flag` int(1) NOT NULL,
`created` datetime DEFAULT CURRENT_TIMESTAMP,
`updated` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`delete_flag` tinyint(1) NOT NULL,
`username` varchar(255) NOT NULL UNIQUE,
`nickname` varchar(255),
`email` varchar(255) UNIQUE,
`phone` varchar(255) UNIQUE,
`icon` varchar(255),
`real_code` varchar(32),
`check_code` binary(48),
`status` int NOT NULL COMMENT '状态0ok1disabled',
`used` int,
`used` int NOT NULL DEFAULT 0,
`space` int DEFAULT 300,
PRIMARY KEY (`id`) USING BTREE
@ -28,16 +33,17 @@ CREATE TABLE IF NOT EXISTS `user`
CREATE TABLE IF NOT EXISTS `app`
(
`id` varchar(32) NOT NULL,
`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`delete_flag` int(1) NOT NULL,
`created` datetime DEFAULT CURRENT_TIMESTAMP,
`updated` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`delete_flag` tinyint(1) NOT NULL,
`key` varchar(32) NOT NULL,
`name` varchar(255) NOT NULL,
`icon` varchar(255),
`des` varchar(255),
`user_count` int NOT NULL,
`hide` int(1) NOT NULL,
`regist` int(1) NOT NULL,
`user_count` int NOT NULL DEFAULT 0,
`hide` tinyint(1) NOT NULL DEFAULT 0,
`join_method` varchar(32) NOT NULL DEFAULT 'auto',
`role_id` varchar(32),
`redirect` varchar(255),
@ -48,8 +54,8 @@ CREATE TABLE IF NOT EXISTS `app`
CREATE TABLE IF NOT EXISTS `app_user`
(
`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created` datetime DEFAULT CURRENT_TIMESTAMP,
`updated` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`app_id` varchar(32) NOT NULL,
`user_id` varchar(32) NOT NULL,
@ -65,9 +71,9 @@ CREATE TABLE IF NOT EXISTS `app_user`
CREATE TABLE IF NOT EXISTS `role`
(
`id` varchar(32) NOT NULL,
`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`delete_flag` int(1) NOT NULL,
`created` datetime DEFAULT CURRENT_TIMESTAMP,
`updated` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`delete_flag` tinyint(1) NOT NULL,
`app_id` varchar(32) NOT NULL,
`name` varchar(255) NOT NULL,
@ -80,8 +86,8 @@ CREATE TABLE IF NOT EXISTS `role`
CREATE TABLE IF NOT EXISTS `user_role`
(
`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created` datetime DEFAULT CURRENT_TIMESTAMP,
`updated` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`user_id` varchar(32) NOT NULL,
`role_id` varchar(32) NOT NULL,
@ -94,9 +100,9 @@ CREATE TABLE IF NOT EXISTS `user_role`
CREATE TABLE IF NOT EXISTS `resource`
(
`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`delete_flag` int(1) NOT NULL,
`created` datetime DEFAULT CURRENT_TIMESTAMP,
`updated` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`delete_flag` tinyint(1) NOT NULL,
`app_id` varchar(32) NOT NULL,
`name` varchar(32) NOT NULL,
@ -110,9 +116,9 @@ CREATE TABLE IF NOT EXISTS `resource`
CREATE TABLE IF NOT EXISTS `access`
(
`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`delete_flag` int(1) NOT NULL,
`created` datetime DEFAULT CURRENT_TIMESTAMP,
`updated` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`delete_flag` tinyint(1) NOT NULL,
`app_id` varchar(32) NOT NULL,
`name` varchar(32) NOT NULL,
@ -128,20 +134,19 @@ CREATE TABLE IF NOT EXISTS `access`
FOREIGN KEY (`app_id`,`name`) REFERENCES `resource`(`app_id`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `app`
ADD FOREIGN KEY (`role_id`) REFERENCES `role`(`id`);
INSERT INTO `user` (`id`, `username`)
VALUES ('user0', 'name');
INSERT INTO `app` (`id`, `name`)
VALUES ('app0', 'a0');
INSERT INTO `app` (`id`, `name`, `key`, `role_id`)
VALUES ('FR9P5t8debxc11aFF', 'oa', 'AMpjwQHwVjGsb1WC4WG6', '1lytMwQL4uiNd0vsc');
INSERT INTO `resource` (`app_id`, `name`)
VALUES ('app0', 'sound');
VALUES ('FR9P5t8debxc11aFF', 'app');
INSERT INTO `role` (`id`, `app_id`)
VALUES ('role0', 'app0');
INSERT INTO `role` (`id`, `app_id`, `name`)
VALUES ('1lytMwQL4uiNd0vsc', 'FR9P5t8debxc11aFF', 'admin');
INSERT INTO `access` (`app_id`, `name`, `role_id`, `user_id`)
VALUES ('app0', 'sound', NULL, 'user0');
VALUES ('FR9P5t8debxc11aFF', 'app', '1lytMwQL4uiNd0vsc', null);
ALTER TABLE `app`
ADD FOREIGN KEY (`role_id`) REFERENCES `role`(`id`);

@ -0,0 +1,6 @@
//
// access.rs
// Copyright (C) 2022 veypi <i@veypi.com>
// 2022-07-09 03:10
// Distributed under terms of the Apache license.
//

@ -0,0 +1,6 @@
//
// app.rs
// Copyright (C) 2022 veypi <i@veypi.com>
// 2022-07-09 03:10
// Distributed under terms of the Apache license.
//

@ -5,8 +5,14 @@
// Distributed under terms of the Apache license.
//
//
mod access;
mod app;
mod resource;
mod role;
mod user;
use crate::{Error, Result};
use actix_web::{get, web, Responder};
use actix_web::{get, web};
#[get("/hello/{name}")]
async fn greet(name: web::Path<u32>) -> Result<String> {
@ -18,12 +24,11 @@ async fn greet(name: web::Path<u32>) -> Result<String> {
}
}
#[get("/topic/derive")]
async fn hello() -> impl Responder {
"Hello World!"
}
pub fn routes(cfg: &mut web::ServiceConfig) {
cfg.service(user::get)
.service(user::list)
.service(user::register)
.service(user::login)
.service(user::delete);
cfg.service(greet);
cfg.service(hello);
}

@ -0,0 +1,6 @@
//
// resource.rs
// Copyright (C) 2022 veypi <i@veypi.com>
// 2022-07-09 03:09
// Distributed under terms of the Apache license.
//

@ -0,0 +1,6 @@
//
// role.rs
// Copyright (C) 2022 veypi <i@veypi.com>
// 2022-07-09 03:10
// Distributed under terms of the Apache license.
//

@ -0,0 +1,107 @@
//
// user.rs
// Copyright (C) 2022 veypi <i@veypi.com>
// 2022-07-09 03:10
// Distributed under terms of the Apache license.
//
use crate::{dbtx, models, Error, Result, CONFIG, DB};
use actix_web::{delete, get, head, post, web, Responder};
use base64;
use rbatis::crud::CRUD;
use serde::{Deserialize, Serialize};
use tracing::info;
#[get("/user/{id}")]
pub async fn get(id: web::Path<String>) -> Result<models::User> {
let n = id.into_inner();
if !n.is_empty() {
let u: Option<models::User> = DB.fetch_by_column("id", &n).await?;
match u {
Some(u) => {
info!("{:#?}", u.token());
return Ok(u);
}
None => Err(Error::NotFound(format!("user {}", n))),
}
} else {
Err(Error::Missing("id".to_string()))
}
}
#[get("/user/")]
pub async fn list() -> impl Responder {
let result: Vec<models::User> = DB.fetch_list().await.unwrap();
web::Json(result)
}
#[derive(Debug, Deserialize, Serialize)]
pub struct LoginOpt {
typ: Option<String>,
password: String,
}
#[head("/user/{id}")]
pub async fn login(q: web::Query<LoginOpt>, id: web::Path<String>) -> impl Responder {
let id = id.into_inner();
let q = q.into_inner();
info!("{} try to login{:#?}", id, q);
let mut w = DB.new_wrapper();
match q.typ {
_ => w = w.eq("username", id),
}
let u: Option<models::User> = DB.fetch_by_wrapper(w).await.unwrap();
info!("{:#?}", u);
""
}
#[derive(Debug, Deserialize, Serialize)]
pub struct RegisterOpt {
username: String,
password: String,
}
#[post("/user/")]
pub async fn register(q: web::Json<RegisterOpt>) -> Result<String> {
let q = q.into_inner();
// let mut tx = dbtx().await;
println!("{:#?}", q);
let u: Option<models::User> = DB.fetch_by_column("username", &q.username).await.unwrap();
let u: models::User = match u {
Some(_) => return Err(Error::ArgDuplicated(format!("username: {}", q.username))),
None => {
let mut u = models::User::default();
u.username = q.username.clone();
let p = match base64::decode(q.password.as_bytes()) {
Err(_) => return Err(Error::ArgInvalid("password".to_string())),
Ok(p) => p,
};
let p = match std::str::from_utf8(&p) {
Ok(p) => p,
Err(_) => return Err(Error::ArgInvalid("password".to_string())),
};
info!("{}", p);
u.update_pass(&p)?;
u
}
};
let oa: Option<models::App> = DB.fetch_by_column("id", CONFIG.uuid.clone()).await?;
let oa = oa.unwrap();
let mut au = models::AppUser::new();
au.app_id = oa.id;
au.user_id = u.id.clone();
match oa.join_method {
models::app::AppJoin::Disabled => return Err(Error::AppDisabledRegister),
models::app::AppJoin::Auto => au.status = models::app::AUStatus::OK,
models::app::AppJoin::Applying => au.status = models::app::AUStatus::Applying,
}
DB.save(&u, &[]).await?;
DB.save(&au, &[]).await?;
Ok("ok".to_string())
}
#[delete("/user/")]
pub async fn delete() -> impl Responder {
""
}

@ -16,10 +16,11 @@ use std::{
use clap::{Args, Parser, Subcommand};
use lazy_static::lazy_static;
use rbatis::rbatis::Rbatis;
use rbatis::{logic_delete::RbatisLogicDeletePlugin, rbatis::Rbatis};
use tracing::log::warn;
lazy_static! {
// Rbatis是线程、协程安全的运行时的方法是Send+Sync无需担心线程竞争
pub static ref DB:Rbatis=Rbatis::new();
pub static ref DB:Rbatis= new_db();
}
lazy_static! {
@ -61,6 +62,8 @@ impl AppCli {
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ApplicationConfig {
pub uuid: String,
pub key: String,
pub debug: bool,
pub server_url: String,
pub db_url: String,
@ -98,6 +101,8 @@ impl ApplicationConfig {
}
pub fn defaut() -> Self {
Self {
uuid: "FR9P5t8debxc11aFF".to_string(),
key: "AMpjwQHwVjGsb1WC4WG6".to_string(),
debug: true,
server_url: "127.0.0.1:4001".to_string(),
db_url: "127.0.0.1:3306".to_string(),
@ -124,18 +129,29 @@ impl ApplicationConfig {
}
}
fn new_db() -> rbatis::rbatis::Rbatis {
let mut rb = rbatis::rbatis::Rbatis::new();
rb.logic_plugin = Some(Box::new(RbatisLogicDeletePlugin::new("delete_flag")));
rb
}
pub async fn dbtx() -> rbatis::executor::RBatisTxExecutorGuard<'static> {
DB.acquire_begin()
.await
.unwrap()
.defer_async(|mut tx1| async move {
if !tx1.is_done() {
_ = tx1.rollback().await;
warn!("db rollback!")
}
})
}
struct FormatTime;
impl tracing_subscriber::fmt::time::FormatTime for FormatTime {
fn format_time(&self, w: &mut tracing_subscriber::fmt::format::Writer<'_>) -> std::fmt::Result {
let d =
time::OffsetDateTime::now_utc().to_offset(time::UtcOffset::from_hms(8, 0, 0).unwrap());
w.write_str(&format!(
"{} {}:{}:{}",
d.date(),
d.hour(),
d.minute(),
d.second()
))
let d = chrono::Local::now();
w.write_str(&d.format("%Y-%m-%d %H:%M:%S").to_string())
}
}

@ -7,7 +7,8 @@
pub mod api;
mod cfg;
pub mod libs;
pub mod models;
mod result;
pub use cfg::{init_log, ApplicationConfig, Clis, CLI, CONFIG, DB};
pub use cfg::{dbtx, init_log, ApplicationConfig, Clis, CLI, CONFIG, DB};
pub use result::{Error, Result};

@ -0,0 +1,70 @@
//
// mod.rs
// Copyright (C) 2022 veypi <i@veypi.com>
// 2022-07-12 14:22
// Distributed under terms of the Apache license.
//
use std::future::{ready, Ready};
use actix_web::{
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
Error,
};
use futures_util::future::LocalBoxFuture;
use tracing::info;
// There are two steps in middleware processing.
// 1. Middleware initialization, middleware factory gets called with
// next service in chain as parameter.
// 2. Middleware's call method gets called with normal request.
pub struct SayHi;
// Middleware factory is `Transform` trait
// `S` - type of the next service
// `B` - type of response's body
impl<S, B> Transform<S, ServiceRequest> for SayHi
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = SayHiMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(SayHiMiddleware { service }))
}
}
pub struct SayHiMiddleware<S> {
service: S,
}
impl<S, B> Service<ServiceRequest> for SayHiMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
println!("Hi from start. You requested: {}", req.path());
let fut = self.service.call(req);
Box::pin(async move {
let res = fut.await?;
println!("Hi from response");
Ok(res)
})
}
}

@ -4,10 +4,10 @@
// 2022-07-07 23:51
// Distributed under terms of the Apache license.
//
use actix_web::{middleware, web, App, HttpServer};
use oab::{api, init_log, models, Clis, Result, CLI, CONFIG};
use tracing::info;
use tracing::{info, warn};
#[tokio::main]
async fn main() -> Result<()> {
@ -31,10 +31,26 @@ async fn web() -> Result<()> {
std::env::set_var("RUST_BACKTRACE", "1");
let serv = HttpServer::new(move || {
let logger = middleware::Logger::default();
let json_config = web::JsonConfig::default()
.limit(4096)
.error_handler(|err, _req|{
// create custom error response
// oab::Error::InternalServerError
warn!("{:#?}", err);
actix_web::error::InternalError::from_response(
err,
actix_web::HttpResponse::Conflict().finish(),
)
.into()
});
let app = App::new();
app.wrap(logger)
.wrap(middleware::Compress::default())
.service(web::scope("api").configure(api::routes))
.service(
web::scope("api")
.app_data(json_config)
.configure(api::routes),
)
});
info!("listen to {}", CONFIG.server_url);
serv.bind(CONFIG.server_url.clone())?.run().await?;

@ -6,6 +6,15 @@
//
use rbatis::{crud_table, DateTimeNative};
use serde::{Deserialize, Serialize};
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
#[serde(rename_all = "lowercase")]
pub enum AppJoin {
Auto,
Disabled,
Applying,
}
#[crud_table]
#[derive(Debug, Clone)]
@ -21,11 +30,11 @@ pub struct App {
pub user_count: usize,
pub hide: bool,
pub register: bool,
pub join_method: AppJoin,
pub role_id: Option<String>,
pub redirect: Option<String>,
pub status: usize,
pub status: i64,
}
impl App {
@ -41,7 +50,7 @@ impl App {
icon: None,
user_count: 0,
hide: false,
register: false,
join_method: AppJoin::Auto,
role_id: None,
redirect: None,
status: 0,
@ -49,7 +58,8 @@ impl App {
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "lowercase")]
pub enum AUStatus {
OK,
Disabled,
@ -58,7 +68,7 @@ pub enum AUStatus {
}
#[crud_table]
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AppUser {
pub created: Option<DateTimeNative>,
pub updated: Option<DateTimeNative>,

@ -5,7 +5,7 @@
// Distributed under terms of the MIT license.
//
mod app;
pub mod app;
mod role;
mod user;
use std::{fs::File, io::Read};

@ -6,7 +6,43 @@
//
//
use actix_web::ResponseError;
use chrono::{prelude::*, Duration};
use generic_array::typenum::U32;
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
use rbatis::{crud_table, DateTimeNative};
use serde::{Deserialize, Serialize};
use tracing::info;
use crate::{Error, Result};
use aes_gcm::aead::{Aead, NewAead};
use aes_gcm::{Aes256Gcm, Key, Nonce};
use block_padding::{Padding, Pkcs7};
use generic_array::{ArrayLength, GenericArray};
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
fn rand_str(l: usize) -> String {
thread_rng()
.sample_iter(&Alphanumeric)
.take(l)
.map(char::from)
.collect()
}
// fn padding<u8, N: ArrayLength<u8>>(s: &str) -> GenericArray<u8, N> {
// let msg = s.as_bytes();
// let pos = msg.len();
// let mut block: GenericArray<u8, U32> = [0xff; 32].into();
// block[..pos].copy_from_slice(msg);
// Pkcs7::pad(&mut block, pos);
// assert_eq!(&block[..], b"test\x04\x04\x04\x04");
// let res = Pkcs7::unpad(&block).unwrap();
// assert_eq!(res, msg);
// block
// }
#[crud_table]
#[derive(Debug, Clone)]
@ -16,32 +52,120 @@ pub struct User {
pub updated: Option<DateTimeNative>,
pub delete_flag: bool,
pub username: Option<String>,
pub username: String,
pub nickname: Option<String>,
pub email: Option<String>,
pub phone: Option<String>,
pub icon: Option<String>,
pub real_code: Option<String>,
pub check_code: Option<rbatis::Bytes>,
pub status: usize,
pub used: usize,
pub space: usize,
}
impl User {
pub fn token(&self) -> Token {
let t = Token {
iss: "oa".to_string(),
aud: "".to_string(),
exp: (Utc::now() + Duration::days(4)).timestamp(),
iat: Utc::now().timestamp(),
id: self.id.clone(),
};
t
}
pub fn update_pass(&mut self, p: &str) -> Result<()> {
if p.len() < 6 || p.len() > 32 {
return Err(Error::ArgInvalid("password".to_string()));
}
let p = p.as_bytes();
let mut key_block: GenericArray<u8, U32> = [0xff; 32].into();
key_block[..p.len()].copy_from_slice(p);
Pkcs7::pad(&mut key_block, p.len());
// key 32 Byte
let key = Key::from_slice(&key_block);
let cipher = Aes256Gcm::new(&key);
// 12 Byte
// 96-bits; unique per message
let nonce = Nonce::from_slice(&self.id.as_bytes()[..12]);
let real = rand_str(32);
let ciphertext = cipher.encrypt(nonce, real.as_bytes().as_ref())?;
self.check_code = Some(ciphertext);
self.real_code = Some(real);
// let plaintext = cipher.decrypt(nonce, ciphertext.as_ref())?;
// let plaintext = std::str::from_utf8(&plaintext).unwrap();
// info!("123123{:?}\n{:?}\n", real, plaintext);
Ok(())
}
}
impl Default for User {
fn default() -> Self {
Self {
id: rbatis::plugin::object_id::ObjectId::new().to_string(),
created: None,
created: Some(DateTimeNative::now()),
updated: None,
delete_flag: false,
username: None,
username: "".to_string(),
nickname: None,
email: None,
phone: None,
icon: None,
check_code: None,
real_code: None,
status: 0,
used: 0,
space: 300,
}
}
}
impl actix_web::Responder for User {
type Body = actix_web::body::BoxBody;
fn respond_to(self, _req: &actix_web::HttpRequest) -> actix_web::HttpResponse<Self::Body> {
match serde_json::to_string(&self) {
Ok(body) => match actix_web::HttpResponse::Ok()
.content_type(actix_web::http::header::ContentType::json())
.message_body(body)
{
Ok(res) => res.map_into_boxed_body(),
Err(err) => Error::from(err).error_response(),
},
Err(_err) => Error::SerdeError.error_response(),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Token {
pub iss: String, // Optional. token 发行者
pub aud: String, // Optional. token 使用者
pub exp: i64, // Required 失效时间
pub iat: i64, // Optional. 发布时间
pub id: String, // 用户id
}
impl Token {
pub fn from(t: &str) -> Result<Self> {
let token = decode::<Self>(
t,
&DecodingKey::from_secret("secret".as_ref()),
&Validation::default(),
)?;
Ok(token.claims)
}
pub fn to_string(&self) -> Result<String> {
let token = encode(
&Header::default(),
self,
&EncodingKey::from_secret("secret".as_ref()),
)?;
Ok(token)
}
}

@ -5,15 +5,20 @@
// Distributed under terms of the Apache license.
//
use actix_web::http::header;
use actix_web::middleware::ErrorHandlerResponse;
use actix_web::ResponseError;
use actix_web::{
error,
dev, error,
http::{header::ContentType, StatusCode},
HttpResponse,
};
use serde::{Deserialize, Serialize};
use thiserror::Error as ThisError;
pub type Result<T> = std::result::Result<T, Error>;
// pub type AsyncResult<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
#[derive(Clone, ThisError, Debug, Deserialize, Serialize)]
@ -31,8 +36,18 @@ pub enum Error {
#[error("Deserialize / Serialize failed")]
SerdeError,
#[error("Resource not found")]
NotFound,
#[error("register disabled")]
AppDisabledRegister,
#[error("missing {0}")]
Missing(String),
#[error("invalid arg {0}")]
ArgInvalid(String),
#[error("duplicated arg {0}")]
ArgDuplicated(String),
#[error("not found {0}")]
NotFound(String),
#[error("timeout")]
Timeout,
@ -94,6 +109,39 @@ impl From<std::io::Error> for Error {
Error::UnsupportedFileType(format!("{:?}", e))
}
}
impl From<actix_web::Error> for Error {
fn from(e: actix_web::Error) -> Self {
Error::BusinessException(format!("{:?}", e))
}
}
impl From<jsonwebtoken::errors::Error> for Error {
fn from(e: jsonwebtoken::errors::Error) -> Self {
Error::BusinessException(format!("{:?}", e))
}
}
impl From<rbatis::error::Error> for Error {
fn from(e: rbatis::error::Error) -> Self {
Error::BusinessException(format!("{:?}", e))
}
}
impl From<aes_gcm::Error> for Error {
fn from(e: aes_gcm::Error) -> Self {
Error::BusinessException(format!("{:?}", e))
}
}
impl From<Box<dyn std::fmt::Display>> for Error {
fn from(e: Box<dyn std::fmt::Display>) -> Self {
Error::BusinessException(format!("{}", e))
}
}
impl actix_web::Responder for Error {
type Body = actix_web::body::BoxBody;
fn respond_to(self, _req: &actix_web::HttpRequest) -> HttpResponse<Self::Body> {
self.error_response()
}
}
impl error::ResponseError for Error {
fn error_response(&self) -> HttpResponse {
@ -107,13 +155,9 @@ impl error::ResponseError for Error {
Error::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
Error::BadRequest => StatusCode::BAD_REQUEST,
Error::Timeout => StatusCode::GATEWAY_TIMEOUT,
Error::NotFound(_) => StatusCode::NOT_FOUND,
Error::NotAuthed => StatusCode::UNAUTHORIZED,
_ => StatusCode::BAD_REQUEST,
}
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct ErrResponse {
pub code: Error,
pub detail: String,
}

@ -1,5 +1,6 @@
import axios from 'axios'
import {store} from '@/store'
import { store } from '@/store'
import msg from '@veypi/msg'
function getQueryVariable(variable: string) {
@ -36,29 +37,30 @@ function baseRequests(url: string, method: any = 'GET', query: any, data: any, s
window.location.href = res.headers.redirect_url
return
}
console.log(res)
if (method === 'HEAD') {
success(res.headers)
} else {
success(res.data)
success(res)
}
})
.catch((e: any) => {
if (e.response && e.response.status === 401) {
if (typeof fail === 'function') {
fail(e.response)
return
}
let code = e.response.status
if (code === 400) {
msg.Warn(e.response.data)
return
} else if (code === 401) {
console.log(e)
store.commit('user/logout')
return
}
console.log(e)
if (e.response && e.response.status === 500) {
} else if (code === 500) {
return
}
if (typeof fail === 'function') {
fail(e.response)
} else if (e.response && e.response.status === 400) {
console.log(400)
} else {
console.log(e.request)
}
console.log(e)
})
}

@ -1,4 +1,3 @@
import {store} from "@/store"
export type SuccessFunction<T> = (e: any) => void;
export type FailedFunction<T> = (e: any) => void;
@ -22,42 +21,6 @@ export class Interface {
}
Start(success?: SuccessFunction<any>, fail?: FailedFunction<any>) {
const newFail = function (data: any) {
if (data) {
if (data.code === 40001) {
// no login
store.commit('user/logout')
return
// @ts-ignore
} else if (data.code === 42011 && window.$msg) {
// @ts-ignore
window.$msg.warning('无权限')
}
}
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
if (data && data.code && Code[data.code]) {
}
if (fail) {
fail(data.err)
} else {
// @ts-ignore
window.$msg.warning(data.err)
}
}
const newSuccess = function (data: any) {
if (Number(data.status) === 1) {
if (success) {
success(data.content)
} else {
// @ts-ignore
window.$msg.warning('ok')
}
} else {
newFail(data)
}
}
this.method(this.api, this.data, newSuccess, newFail, this.header)
this.method(this.api, this.data, success, fail, this.header)
}
}

@ -1,7 +1,7 @@
import {Base64} from 'js-base64'
import {Interface} from './interface'
import { Base64 } from 'js-base64'
import { Interface } from './interface'
import ajax from './ajax'
import {BaseUrl} from './setting'
import { BaseUrl } from './setting'
export default {
local: BaseUrl + 'user/',
@ -14,12 +14,12 @@ export default {
},
login(username: string, password: string) {
return new Interface(ajax.head, this.local + username, {
UidType: 'username',
typ: 'username',
password: Base64.encode(password),
})
},
search(q: string) {
return new Interface(ajax.get, this.local, {username: q})
return new Interface(ajax.get, this.local, { username: q })
},
get(id: number) {
return new Interface(ajax.get, this.local + id)

@ -1,93 +1,92 @@
<template>
<div class="flex items-center justify-center">
<div
:style="{background:Theme.me.lightBox, 'box-shadow': Theme.me.lightBoxShadow}"
class="px-10 pb-9 pt-28 rounded-xl w-96">
<n-form label-width="70px" label-align="left" :model="data" ref="form_ref" label-placement="left" :rules="rules">
<n-form-item required label="用户名" path="username">
<n-input @keydown.enter="divs[1].focus()" :ref="el => {if (el)divs[0]=el}"
v-model:value="data.username"></n-input>
</n-form-item>
<n-form-item required label="密码" path="password">
<n-input @keydown.enter="divs[2].focus()" :ref="el => {if (el) divs[1]=el}" v-model:value="data.password"
type="password"></n-input>
</n-form-item>
<n-form-item required label="重复密码" path="pass">
<n-input @keydown.enter="register" :ref="el => {if (el) divs[2]=el}" v-model:value="data.pass"
type="password"></n-input>
</n-form-item>
<div class="flex justify-around mt-4">
<n-button @click="register"></n-button>
<div class="flex items-center justify-center">
<div :style="{ background: Theme.me.lightBox, 'box-shadow': Theme.me.lightBoxShadow }"
class="px-10 pb-9 pt-28 rounded-xl w-96">
<n-form label-width="70px" label-align="left" :model="data" ref="form_ref" label-placement="left"
:rules="rules">
<n-form-item required label="用户名" path="username">
<n-input @keydown.enter="divs[1].focus()" :ref="el => { if (el) divs[0] = el }"
v-model:value="data.username"></n-input>
</n-form-item>
<n-form-item required label="密码" path="password">
<n-input @keydown.enter="divs[2].focus()" :ref="el => { if (el) divs[1] = el }"
v-model:value="data.password" type="password"></n-input>
</n-form-item>
<n-form-item required label="重复密码" path="pass">
<n-input @keydown.enter="register" :ref="el => { if (el) divs[2] = el }" v-model:value="data.pass"
type="password"></n-input>
</n-form-item>
<div class="flex justify-around mt-4">
<n-button @click="register"></n-button>
</div>
</n-form>
</div>
</n-form>
</div>
</div>
</template>
<script lang="ts" setup>
import {onMounted, ref} from "vue";
import {Theme} from "@/theme";
import {useMessage} from 'naive-ui'
import { onMounted, ref } from "vue";
import { Theme } from "@/theme";
import api from "@/api"
import {useRouter} from "vue-router";
import { useRouter } from "vue-router";
import msg from '@veypi/msg'
let msg = useMessage()
const router = useRouter()
const divs = ref([])
let form_ref = ref(null)
let data = ref({
username: '',
password: '',
pass: ''
username: '',
password: '',
pass: ''
})
let rules = {
username: [
{
required: true,
validator(r: any, v: any) {
return (v && v.length >= 3 && v.length <= 16) || new Error('长度要求3~16')
},
trigger: ['input', 'blur']
}
],
password: [{
required: true,
validator(r: any, v: any) {
return (v && v.length >= 6 && v.length <= 16) || new Error('长度要求6~16')
},
trigger: ['input', 'blur']
}],
pass: [
{
required: true,
validator(r: any, v: any) {
return (v && v === data.value.password) || new Error('密码不正确')
},
trigger: ['input', 'blur']
}
]
username: [
{
required: true,
validator(r: any, v: any) {
return (v && v.length >= 3 && v.length <= 16) || new Error('长度要求3~16')
},
trigger: ['input', 'blur']
}
],
password: [{
required: true,
validator(r: any, v: any) {
return (v && v.length >= 6 && v.length <= 16) || new Error('长度要求6~16')
},
trigger: ['input', 'blur']
}],
pass: [
{
required: true,
validator(r: any, v: any) {
return (v && v === data.value.password) || new Error('密码不正确')
},
trigger: ['input', 'blur']
}
]
}
function register() {
// @ts-ignore
form_ref.value.validate((e: any) => {
if (!e) {
api.user.register(data.value.username, data.value.password).Start((url: string) => {
msg.success('注册成功')
router.push({name: 'login'})
}, e => {
console.log(e)
msg.warning('注册失败:' + e)
})
}
})
// @ts-ignore
form_ref.value.validate((e: any) => {
if (!e) {
api.user.register(data.value.username, data.value.password).Start((url: string) => {
msg.Info('注册成功')
router.push({ name: 'login' })
}, e => {
console.log(e)
msg.Warn('注册失败:' + e.data)
})
}
})
}
onMounted(() => {
if (divs.value[0]) {
// @ts-ignore
divs.value[0].focus()
}
if (divs.value[0]) {
// @ts-ignore
divs.value[0].focus()
}
})
</script>

Loading…
Cancel
Save