Teo是以結構為核心的聲明式網路後端開發框架,支持Node.js、Python和Rust語言,支持手動編寫和AI編寫。它能夠節省80%的開發時間,大大縮短開發時間和開發成本,符合當下的技術環境和社會環境,是新一代的網路開發框架。
在0.2.16版本中,我們新增了使用者登入、token驗證等功能。多的不說,請看演示。
Teo schema
聲明一個伺服器,包含增刪改查分組聚合是非常簡單的,甚至不需要編程程式碼,使用編程程式碼,開發者可以存取ORM API和定義自訂的路由,編寫自訂的路由先進後出中介軟體。
connector {
provider: .sqlite,
url:"sqlite:./database.sqlite"
}
server {
bind: ("0.0.0.0", 5052)
}
model User {
@id @autoIncrement @readonly
id: Int
@unique @onSet($if($presents, $isEmail))
email: String
@writeonly @onSet($presents.bcrypt.salt)
password: String
}
如上所示,聲明一個含有信箱和密碼的使用者表,並產生增刪改查分組聚合等API,是非常容易的。在schema中,我們也指定了伺服器監聽的埠和連線的資料庫。Teo采用連線池來管理資料庫連線,非常的高效和效能。 使用命令「 teo serve」 即可啟動伺服器。
使用者登入
為這個使用者表增加登入功能,非常的簡單,請看更新後的程式碼。
connector {
provider: .sqlite,
url:"sqlite:./database.sqlite"
}
server {
bind: ("0.0.0.0", 5052)
}
@identity.tokenIssuer($identity.jwt(expired:3600 * 24 * 365))
@identity.jwtSecret(ENV["JWT_SECRET"]!)
model User {
@id @autoIncrement @readonly
id: Int
@unique @onSet($if($presents, $isEmail)) @identity.id
email: String
@writeonly @onSet($presents.bcrypt.salt)
@identity.checker($get(.value).presents.bcrypt.verify($self.get(.password).presents))
password: String
include handler identity.signIn
include handler identity.identity
}
middlewares [identity.identityFromJwt(secret: ENV["JWT_SECRET"]!)]
我們增加了一些修飾詞,指定了token的encode方式,這裏采用業內最流行的JWT token,並指定了過期時間。我們標記了采用信箱登入,采用密碼驗證,並且增加了兩個由模版handler構成的新的route handler。這兩個route handler完全不需要手動程式碼編寫。在檔內增加中介軟體,這個中介軟體會驗證token並且設定當前的登入者。這段程式碼,總共比之前的沒有登入功能的時候,增加了5行程式碼。
輔助驗證資訊
在實際開發中,我們在登入的時候,經常要使用者點選圖片驗證碼或輸入一些來自圖片中的文字。這些輔助的驗證資訊,也可以透過Teo的API進行傳遞,話不多說,上例子。
connector {
provider: .sqlite,
url:"sqlite:./database.sqlite"
}
server {
bind: ("0.0.0.0", 5052)
}
@identity.tokenIssuer($identity.jwt(expired:3600 * 24 * 365))
@identity.jwtSecret(ENV["JWT_SECRET"]!)
model User {
@id @autoIncrement @readonly
id: Int
@unique @onSet($if($presents, $isEmail)) @identity.id
email: String
@writeonly @onSet($presents.bcrypt.salt)
@identity.checker(
$do($get(.value).presents.bcrypt.verify($self.get(.password).presents))
.do($get(.companions).presents.get(.imageAuthToken).presents))
password: String
@virtual @writeonly @identity.companion
imageAuthToken: String?
include handler identity.signIn
include handler identity.identity
}
middlewares [identity.identityFromJwt(secret: ENV["JWT_SECRET"]!)]
我們又增加了四行程式碼。imageAuthToken是一個虛擬欄位,它不會在資料庫中出現,但是卻會在請求中出現,這樣的欄位是配合API功能的。比如,輸入舊密碼更改當前密碼,這個功能,就需要定義一個oldPassword這樣的虛擬欄位。在這個例子中,我們只是檢查imageAuthToken是否存在,而實際套用中,我們透過編寫pipeline item跟第三方介面驗證驗證碼即可。
動態的token過期時間
有一些論壇網站,token過期時間是使用者自己選擇的,現在有這個需求的產品很少,但是Teo同樣支持。
connector {
provider: .sqlite,
url:"sqlite:./database.sqlite"
}
server {
bind: ("0.0.0.0", 5052)
}
@identity.tokenIssuer($identity.jwt(expired: $get(.expired).presents))
@identity.jwtSecret(ENV["JWT_SECRET"]!)
model User {
@id @autoIncrement @readonly
id: Int
@unique @onSet($if($presents, $isEmail)) @identity.id
email: String
@writeonly @onSet($presents.bcrypt.salt)
@identity.checker(
$do($get(.value).presents.bcrypt.verify($self.get(.password).presents))
.do($get(.companions).presents.get(.imageAuthToken).presents))
password: String
@virtual @writeonly @identity.companion
imageAuthToken: String?
@virtual @writeonly @identity.companion
expired: Int64?
include handler identity.signIn
include handler identity.identity
}
middlewares [identity.identityFromJwt(secret: ENV["JWT_SECRET"]!)]
把JWT的expire參數改為動態獲取,就可以得到使用者傳來的expire參數了。如果expire參數填一個很低的秒數,這個token很快就會過期。
帳號封禁
實作封禁帳號是很簡單的,利用聲明式的優勢。告訴Teo,什麽樣的帳號判定為被封禁即可。
connector {
provider: .sqlite,
url: "sqlite:./database.sqlite"
}
server {
bind: ("0.0.0.0", 5052)
}
@identity.tokenIssuer($identity.jwt(expired: $get(.expired).presents))
@identity.jwtSecret(ENV["JWT_SECRET"]!)
@identity.validateAccount($get(.enabled).presents.eq(true))
model User {
@id @autoIncrement @readonly
id: Int
@unique @onSet($if($presents, $isEmail)) @identity.id
email: String
@writeonly @onSet($presents.bcrypt.salt)
@identity.checker(
$do($get(.value).presents.bcrypt.verify($self.get(.password).presents))
.do($get(.companions).presents.get(.imageAuthToken).presents))
password: String
@virtual @writeonly @identity.companion
imageAuthToken: String?
@virtual @writeonly @identity.companion
expired: Int64?
@migration(default: true)
enabled: Bool
include handler identity.signIn
include handler identity.identity
}
middlewares [identity.identityFromJwt(secret: ENV["JWT_SECRET"]!)]
在這裏更新的例子中,透過新增的一行模型聲明,Teo知道了,enabled欄位為false的帳號即是封禁帳號。封禁的帳號不能進行登入,其token也會失效。
第三方身份整合
實際開發中,我們經常要做微信登入,支付寶登入等平台身份登入。具體的前端跳轉邏輯,我們不做演示。在後端,繫結帳號就是更新欄位,我們演示如何使用三方token進行登入。
connector {
provider: .sqlite,
url: "sqlite:./database.sqlite"
}
server {
bind: ("0.0.0.0", 5052)
}
@identity.tokenIssuer($identity.jwt(expired: $get(.expired).presents))
@identity.jwtSecret(ENV["JWT_SECRET"]!)
@identity.validateAccount(
$message($get(.enabled).presents.eq(true), "this account is blocked"))
model User {
@id @autoIncrement @readonly
id: Int
@unique @onSet($if($presents, $isEmail)) @identity.id
email: String
@writeonly @onSet($presents.bcrypt.salt)
@identity.checker(
$do($get(.value).presents.bcrypt.verify($self.get(.password).presents))
.do($get(.companions).presents.get(.imageAuthToken).presents))
password: String
@virtual @writeonly @identity.companion
imageAuthToken: String?
@virtual @writeonly @identity.companion
expired: Int64?
@migration(default: true) @default(true)
enabled: Bool
@identity.id @unique
thirdPartyId: String?
@virtual @writeonly @identity.checker($get(.value).presents.valid)
thirdPartyToken: String?
include handler identity.signIn
include handler identity.identity
}
middlewares [identity.identityFromJwt(secret: ENV["JWT_SECRET"]!)]
在表中新增三方id和三方token,使用自訂的pipeline item與三方溝通獲得token是否有效。一旦有效,就給使用者在我們系統中登入,獲得我們的token。這時,進行signIn請求,我們不用輸入信箱和密碼,輸入三方id和token即可。
手機驗證碼/信箱驗證碼/手機密碼/信箱密碼
Teo支持任意的身份欄位和驗證方式相匹配。我們來看一個驗證碼驗證的案例。這一次需要編寫編程程式碼。
connector {
provider: .sqlite,
url: "sqlite:./database.sqlite"
}
server {
bind: ("0.0.0.0", 5052)
}
entity {
provider: .rust,
dest: "./src/entities"
}
declare pipeline item validateAuthCode<T>: T -> String
@identity.tokenIssuer($identity.jwt(expired: $get(.expired).presents))
@identity.jwtSecret(ENV["JWT_SECRET"]!)
@identity.validateAccount(
$message($get(.enabled).presents.eq(true), "this account is blocked"))
model User {
@id@autoIncrement@readonly
id: Int
@unique@onSet($if($presents, $isEmail)) @identity.id
@presentWithout(.phoneNumber)
email: String?
@writeonly@onSet($presents.bcrypt.salt)
@identity.checker(
$do($get(.value).presents.bcrypt.verify($self.get(.password).presents))
.do($get(.companions).presents.get(.imageAuthToken).presents))
password: String?
@virtual@writeonly@identity.companion
imageAuthToken: String?
@virtual@writeonly@identity.companion
expired: Int64?
@migration(default: true) @default(true)
enabled: Bool
@identity.id @unique
thirdPartyId: String?
@virtual@writeonly@identity.checker($get(.value).presents.valid)
thirdPartyToken: String?
@onSet($if($presents, $regexMatch(/\\+?[0-9]+/))) @identity.id
@presentWithout(.email) @unique
phoneNumber: String?
@virtual@writeonly@identity.checker($validateAuthCode)
authCode: String?
include handler identity.signIn
include handler identity.identity
}
model AuthCode {
@id@autoIncrement@readonly
id: Int
@presentWithout(.phoneNumber) @unique
email: String?
@presentWithout(.email) @unique
phoneNumber: String?
@onSave($randomDigits(4))
code: String
}
middlewares [identity.identityFromJwt(secret: ENV["JWT_SECRET"]!)]
我們新增了一張AuthCode表,用來保存使用者收到的驗證碼。我們用編程程式碼存取Teo的ORM API來進行驗證碼驗證。程式碼如下,有三個語言版本,選擇你采用的語言即可。
Node.js版本
import { App } from'@teocloud/teo'
import { AuthCodeWhereUniqueInput, Teo, User } from'./entities'
const app = new App()
app.mainNamespace().defineValidatorPipelineItem(
"validateAuthCode",
async (checkArgs: any, _, user: User, teo: Teo) => {
const finder: AuthCodeWhereUniqueInput = {}
if (checkArgs.ids.email) {
finder.email = user.email!
}
if (checkArgs.ids.phoneNumber) {
finder.phoneNumber = user.phoneNumber!
}
const authCode = await teo.authCode.findUnique({
where: finder
})
if (!authCode) {
return"auth code not found"
}
if (authCode.code !== checkArgs.value) {
return"auth code is wrong"
}
})
app.run()
Python版本
from __future__ import annotations
from typing import Any
from asyncio import run
from teo import App
from entities import Teo, User
asyncdefmain():
app = App()
asyncdefvalidate_auth_code(checker_args: dict[str, Any], _, user: User, teo: Teo):
finder = {}
if checker_args['ids'].get('email') isnotNone:
finder['email'] = user.email
if checker_args['ids'].get('phoneNumber') isnotNone:
finder['phoneNumber'] = user.phone_number
auth_code = await teo.auth_code.find_unique({
"where": finder
})
if auth_code isNone:
return"auth code not found"
if auth_code.code != checker_args.value:
return"auth code is wrong"
app.main_namespace().define_validator_pipeline_item(
"validateAuthCode",
validate_auth_code)
run(main())
Rust版本
pub mod entities;
useentities::{Teo, User};
useindexmap::indexmap;
usetokio::main;
useteo::prelude::{pipeline::item::validator::Validity, App, Result, Value, Error};
#[main]
async fn main() -> Result<()> {
let app = App::new()?;
app.main_namespace_mut().define_validator_pipeline_item("validateAuthCode", move |check_args: Value, user: User, teo: Teo| async move {
let mut finder = Value::Dictionary(indexmap!{});
let check_args = check_args.as_dictionary().unwrap();
let ids = check_args.get("ids").unwrap().as_dictionary().unwrap();
if ids.contains_key("email") {
finder.as_dictionary_mut().unwrap().insert("email".to_owned(), ids.get("email").unwrap().clone());
}
if ids.contains_key("phoneNumber") {
finder.as_dictionary_mut().unwrap().insert("phoneNumber".to_owned(), ids.get("phoneNumber").unwrap().clone());
}
let auth_code = teo.auth_code().find_unique(finder).await?;
match auth_code {
Some(auth_code) => if auth_code.code().as_str() == check_args.get("value").unwrap().as_str().unwrap() {
Ok::<Validity, Error>(Validity::Valid)
} else {
Ok(Validity::Invalid("auth code is wrong".to_owned()))
},
None => Ok(Validity::Invalid("auth code not found".to_owned()))
}
});
app.run().await
}
這三組程式碼做的事情是一樣的,建立一個叫作 "validateAuthCode" 的pipeline item。在schema中,我們看到了這個pipeline item的位置,我們聲明並使用了它。這個方法內部,我們根據使用者輸入的電話號碼或信箱地址,來尋找一個驗證碼,一旦輸入正確,我們給予使用者登入,否則給予使用者錯誤資訊:驗證碼不存在或驗證碼錯誤。
舉一反三
在剛剛展示的例子中,我們列舉了常常容易被想到的token用法。如果實作一個人最多允許幾個token,和invalid某個token,則需要單獨建表並編寫自訂的驗證程式碼,像上面的驗證碼驗證那樣。其他的各種驗證和token方式,也可以透過Teo強大的生命方式來實作。
聲明式的魅力
在這逐步幾次更新的schema中,我們看到了聲明式編程的魅力。一切都那麽簡潔、易讀、可描述。不再有難以看得懂的程式碼。因為聲明式很緊湊,邏輯混亂的bug也很難出現。這樣的程式碼,易於編寫,易於部署。我們的開發者使用者已經開始采用AI編程的方式來編寫Teo伺服器程式碼了。
開發文件
我們的官網內寫有豐富的開發文件,支持白天模式和夜間模式的閱讀。安裝流程,快速開始指南,教程,概念,專題指南和API文件一應俱有。
官網:https://teocloud.io
支持我們
我們從2022年編寫Teo至今,已經接近兩年了,它從最初的僅支持Rust和MongoDB的框架,到現在支持三種語言,支持主流SQL和MongoDB,支持自訂handler和中介軟體,支持生成前端請求客戶端,一路過來真的很辛苦。我們不斷科研創新、探索研究,克服重重技術難題,把它做到現在。它是完全開源免費的框架。我們做它的目的,是讓開發者能過上不卷、沒有太大壓力的好日子,讓創業者和企業能夠節省成本,更容易不被技術所負累,更容易成功。
我們十分需要您的支持,請關註我們的公眾號,在Gitee或GitHub為我們點一顆星。
Gitee: https://gitee.com/teocloud/teo
GitHub: https://github.com/teocloud/teo
聯系我們
在使用過程中,如果遇到任何困難,想要與我們溝通,或是提出功能需求,是非常容易的。帶著點贊截圖和公眾號關註截圖,添加我們的微信群群管微信caofz007,即可加入我們的使用者群。
加入我們團隊
目前我們都是以誌願者形式參與的開發,我是計畫的發起者和主要編寫者,框架核心,語言繫結,編輯器外掛程式,網站都是我全職編寫的。我們需要核心開發,前端開發,編輯器外掛程式開發等人手,來把它做得更好。
我們現在有清晰的開發和推廣,和商業化路線。在Teo具有一定規模和可見的良好未來的時候,即在具有一定底牌、資格、合適的時候,我們會積極努力尋找融資,把它做大。也會給予我們的計畫貢獻者,符合貢獻價值 的獎勵。添加我們的夥伴caofz007這個微信,加入我們,成為我們的一部份。