当前位置: 欣欣网 > 码农

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这个微信,加入我们,成为我们的一部分。