當前位置: 妍妍網 > 碼農

Teo 0.2.16釋出!使用者登入只需5行程式碼,高度可延伸!

2024-03-27碼農

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 @readonlyid: 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 @readonlyid: Int @unique @onSet($if($presents, $isEmail)) @identity.idemail: String @writeonly @onSet($presents.bcrypt.salt) @identity.checker($get(.value).presents.bcrypt.verify($self.get(.password).presents))password: Stringinclude handler identity.signIninclude 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 @readonlyid: Int @unique @onSet($if($presents, $isEmail)) @identity.idemail: 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.companionimageAuthToken: String?include handler identity.signIninclude 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 @readonlyid: Int @unique @onSet($if($presents, $isEmail)) @identity.idemail: 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.companionimageAuthToken: String? @virtual @writeonly @identity.companionexpired: Int64?include handler identity.signIninclude 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 annotationsfrom typing import Anyfrom asyncio import runfrom teo import Appfrom entities import Teo, Userasyncdefmain(): 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.emailif 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這個微信,加入我們,成為我們的一部份。