首页 文章 精选 留言 我的

精选列表

搜索[扩展层],共10000篇文章
优秀的个人博客,低调大师

Android源码剖析之Framework层实战版(Ams管理Activity启动)

本文来自http://blog.csdn.net/liuxian13183/,引用必须注明出处! 讲到实战,就不得不拿两个例子来说明,本篇想拿的是应用最广泛的两个:Ams和Wms,一个管理activity,一个管理窗口,而前面我们已经讲了不少,本篇不再赘述。 关于Ams对activity的管理,无非这几个方面:启动哪个activity、物理按键对activity处理、内存骤减时activity的回收规则,以及暂停activity的一系列操作。 先说如何启动activity?有哪些知识点。 Ams调度activity,运行某activity需要通过Ams决定,一般情况下仅限允许均可执行;记录运行的页面,保存activity一些状态,以便下次启动更加快速,一般放在ActivityRecord,以前叫HistoryRecord;做进程管理,清楚运行哪些进程,各自的状态和信息集合 当然Ams除了管理activity,还会管理其他两个主要数据类,如Process和Task;Process同样也有个ProcessRecord用来记录进程运行信息和状态以及内部有哪些组件(指service、provider这样的),多个apk可以运行在同一进程中,可以通过android:process来设置,一般情况下还是不要,因为手机默认64M内存(不同分辨率不同),内存一般也仅够一个apk使用;而TaskRecord记录activity所在的栈的id、intent和activity的数量,用于对栈进行管理。 AMS常见的默认系统常量: 1、MAX_ACTIVITIES=20: 通过硬件设置,当前页面仅有一个activity存活,其他20个被缓存起来,超过则杀死优先低的 2、MAX_RECENT_TASKS=20:通过硬件设置,栈最多存储20个,超过后最先舍弃最早的栈 3、PAUSE_TIMEOUT=500:启动activity最长时间为500ms,否则关闭该页面,一般表现是“屏幕黑一下” 4、LAUNCH_TIMEOUT=10*1000:启动进程最长10s,否则舍弃 5、PROC_START_TIMEOUT=10*1000:启动进程后10s内必须报告AMS启动状态,否则AMS不承认此进程的存在 以上是最基础的变量,相信在多年开发中,大家对其引起的现象已经历历在目 其次在启动和暂停activity过程中,需要一些变量来保存状态,主要因为AMS是通过Service机制动作,比如 1、mPendingActivityLaunched:即将启动的activity列表 2、mStoppingActivities:A跳入B,B启动后A即被加入此队列,区别于mHistory的A跳入即加入 3、mHistory:所有后台activity,A跳入B即A被加入,或按Home键B被加入,回来按回退键则B被清除;finishing=true 4、mFinishingActivities:上面列表中执行完finish方法的activity,此时并未完全杀死等待被回收 5、mLRUActivities:最近使用的activity,包含mHistory里的activity和已经清除的activity 此外还有HistroyRecord记录mPausingActivity(record对象,记录执行onPause方法的activity),mResumeActivity、mFocusedActvity和mLastPausedActivity等。 上面讲完基础,接下来进程流程,讲讲如何启动Activity? 一般调用方式有四种,点击手机图标、执行startActivityForResult(startActivity同理)、注册硬件监听启动、被action启动 以下是启动Activity的过程和startActivityForResult的启动(startActivity传一个默认的requestCode-1,最后仍然调用startActivityForResult方法,所以放在一起讲) 启动前的权限检查和准备工作 启动Activity的时候往往还需要进行权限检查,以查看其是否符合启动条件。查询不满足条件则返回,否则继续;检查Component是否存在,不满足返回,满足继续,启动startActivityLocked(caller,intent)方法,完成以下几件事 1、检查如果启动自己则直接返回 2、会加入INTENT_FLAG_FORWARD_RESULT标志,用于从C直接返回结果给A,使用方法A调用startActivityForResut->B调用startActivity->C调用setResult直接回到A 3、检查call-ActivityRecord是否存在且有指定权限 4、如果Ams中有IActivityController对象,则通知Controller进行相应控制 5、创建临时HistoryRecord对象,不一定加入mHistory列表,如不关闭则加入 6、检查是否允许切换activity,否则加入mPendingActivityLaunched 7、判断pendingActivity是否有要启动的activity,有则先执行,无则执行该intent 如想了解launchMode,请移步: Android基础之Activity launchMode详解 如想了解Intent,请移步: Intent中的四个重要属性——Action、Data、Category、Extras 以上是对intent的介绍,接下来会再介绍一下task,也就是如何启动,以什么样的规则启动和退出。以下均指launchFlag,标记均以FLAG_ACTIVITY_开头,介绍时会忽略,请注意一下;启动时会依次判断如下标识 1、NO_USER_ACTION:含义无用户交互;基本不用,主要防止一段时间后执行onUserLeaving方法;接下来如果立即启动,就把r.delay.Resume设为true 2、PREVIOUS_IS_TOP:含义上个intent是否位于栈顶,基本不用;然后为activity赋予权限加入缓存;此时区别于launchMode和launchFlag,前者指activity自己声明的启动方式,后者是明显启动者想让activity如何启动,能过intent设置,但两者有相通性 3、NEW_TASK、SINGLE_TASK、SINGLE_INSTANCE:使用这三者均不建议使用startActivityForResut,而只限于使用startActivity即r.resultTo=0,不需要回传数据的情况下;第1个是会新起一个task,即两个task之间最好不进行数据回传;2和3的相同在于,如果已经存在这样的task和component以及其他相同数据如intent,则均跳到相应栈中,不存在则声明一个新的task;不同点在于:2的task里可以包含多个activity,而3仅能包含一个;可能跟每个task内存大小有关,不同功用的activity可以申请不同的task使用,这一点也可以看上面“Android基本之Activity LaunchMode详解” 4、CLEAR_TOP、REORDER_TO_FRONT:前者如自己存在,则清除该栈上面的其他activity;后者仅把自己放在最上面;举例A1->A2->A3,前者启动A2则变成A1->A2,后者启动A2则变成A1->A3->A2 5、NO_HISTORY:不要保存自己,设置A3,则A1->A2->A3->A4,执行完还是A1->A2,皮之不存,毛将焉附。 一般情况下,以上如果调整栈的顺序,那么是可以执行的;如A、B、C三个栈,分别有2个activity,如果从C切换到B,那么是这样的A1->A2->B1->B2->C1->C2,调整后A1->A2->C1->C2->B1->B2。 系统还提供另外一种办法来设置activity的启动方式,那就是 1、android:clearTaskOnLaunch=true/false:是否清除task里的其他activity 2、android:finishOnTaskLaunch:是否关闭task中已有的此activity 3、android:allowTaskReparent:是否将自己带入启动的task中 4、android:alwaysRetainTaskState:是否由系统维护activity状态;一般应用在根activity,每次可以打开最后打开的页面 最近讲的几个东西之间的关系,画了一张图,如下所示 正式启动工作(主要流程均为ActivityThread执行-在attach时初始化) 一、暂停当前activity 1、判断该activity是否存在;然后执行onUserLeaving方法,避免此activity再与物理按键交互,如后退键 2、调用performPauseActivity(告知暂停而非超时等情况,将prev指向自己),先onSaveInstanceState,再执行onPause ·3、报告AMS暂停完毕,通过IPC调用AMS的completePauseActivity方法 二、调用resumeTopActivity方法: 1、如果mHistory有记录且直接启动,否则执行startHomeActivityLocked方法启动主界面程序; 2、系统处于睡眠状态或当前activity未被暂停,则停止; 3、从mStoppingActivities和mWarningVisibleActivities里移除目标对象; 4、将被停止的activity设置为不可见状态,通知activty或task切换 5、判断目录进程是否存在并且activityThread存活,否则执行startSpecificActivityLocked方法;如果进程不存在,则调用 Process类启动新进程,设置pid加入ProcessRecord,启动完之后再通知Ams启动目标Activity,至于启动Process的过程跟调用 暂停或启用activity的过程无异,仅仅参数发生变化而已,以及变量不同和意义不同。当然我们再讲一点,启动进程毕 竟是个重要流程,提取odex文件,前面几篇文章有过介绍,指已经优化过的dex文件,通过Service、Provider和 Broadcast加入引用,创建完成。 6、最终调用performLaunchActivity,执行attach,执行setTheme,几个on方法,拿到DecorView加入Wms,设 置可见。 接下来咱们讲讲停止工作:一般情况下是长时间不使用,或者应用内存紧张,或者启动时设置no_history才会执行,执行过程 而应用与上面生命周期无异,也就是设置不可见,加个mStoppingActivities,异步通知Ams停止,最后调用onStop方法等等, 关闭activity也同样如此;至于关闭的优先级前面似乎没讲,接下来会着重介绍一下。 Android系统如何管理自己内存的?同样可以预习一下,原理协同,再做补充。 一般情况下优化级分为-16到15级,而android默认仅使用0-15级,越小优先级越高,当前可见activity最高为0。 为什么会产生这个优化级,主要android采用的是关闭而不退出,退出而不清理的原则,主要为了二次加载更加快速; 其次是上面停止activity的原因。而这个规则由Ams加权得出,有这样几个变量:是否展示在当前页或是持久化 activity、相应的配合组件、剩余内存量、HOME进程、活跃activity数量和组件等,就像JVM的内存管理规则一样, 详见:Java高级之虚拟机垃圾回收机制 由于Ams无法预知内存的变动(OOM除外),因而采用这套机制:最先是空进程(无activity进程),其次是 activity,再次是前台的配合组件,如Service、Receiver、Provider,最后才是前台activity;而这些操作由OOM Killer 进程直接管理,关于activity回收需要满足以下几种情况: 1、finish操作或crash或anr,通过updateOomAdjLocked方法让其指定优先级,动态调整 2、如果运行的activity超过20个,必须是已经stop的、不可见的、非常驻进程 因而不合理的手机架构也会造成系统的崩溃。PS:持久化对象的在ProcessRecord的persistent变量是否为true LocalActivityManager存在于Activity内部,用来装载和管理activity以及各种状态,维持一个最低的内存消耗;核 心在于它使用UI线程的activityThread来装载指定的 activity;有同学可能会问task越少或者histroyRecord越小,内存 会占用越小吗?这个问题跟手机里装一个app和装n个app重量是否增加是一样的道理,但是越过限制以后(task允许) 内存占用确实会变大,直到出现OOM。 以下是按键方面的内容(锁屏下,基本均不操作) 1、后退键:Activity里监听到onBackPressed方法;一般执行的是finish动作,执行performDestroyActivity方法 2、Home键:Acitivty的onKey方法无法截取它,属于设计原因;Wms中使用PhoneWindowManager类中的 interceptKeyTi截取消息,发现是它,再执行launchHomeFromHotKey;2.0之后添加另一个Home键执行 startDockOrHome方法来监听硬件。 与普通的启动区别在于,能启动特殊的intent,方法在ContextImpl里。而长按震动、关闭所有窗口、会弹出LRT- lasted recenttask,可以调用Ams的getRecentTask来取出,通过弹出的窗体的点击事件,进入相应的应用。 总结一下: 进程启动:1、内核创建进程数据结构,指出其地址总线 2、装载函数,读取代码,拿到数据总线 3、将程序指针指向目标地址入口 虚拟机启动:首次是从Zygote进程fork出来一个子进程,用来加载资源,然后启动SystemSever,用来监 控手机按键和触摸事件,以及管理Ams、Pms、Wms等,最终根据配置文件的HomeActivity,启动它。 Activity启动,然后触发init.rc文件执行main函数,启动一个ActivityThread 这就是所谓的UI线程,由Looper声明一个MessageQueue,接着创建Activity对象,初始化ViewRoot和token,用来分 发消息和接收转换为本地消息,为后面执行Activity的生命周期,创建PhoneWindow,执行attach方法,初始化内部组 件,根据Wms返回的消息,适时的执行生命周期,执行setContentView创建DecorView(View内部也有ViewRoot来设置View的各种属性),由Wms加入到窗口中,设置Visiable,加载结束。 注:每个应用仅有一个ActivityThread来异步处理内部事务,如果出错则App Crash;具体应用启动后,会创建ApplicationThread和ActivityThread,分别用来处理应用事务和Activity事务,并且创建MessageQueue,不停的轮循;AMS通过判断加载某个Activity和资源的加载情况,将消息从手机端通过Binder发给当前应用的ViewRoot对象,通过handler把消息放入消息队列,轮循出来的消息处理,即推动Activity的生命周期执行。 问题1:在应用运行过程中Window有多个吗?是的,每个Activity都会创建Window(见1)。 问题2:在应用运行过程中WindowManager有多个吗?Activity的WindowManager通过系统的WindowManager创建(见2、3、4) Activity类 final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window) { attachBaseContext(context); mFragments.attachHost(null /*parent*/); 1、 mWindow = new PhoneWindow(this, window); mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { mWindow.setSoftInputMode(info.softInputMode); } if (info.uiOptions != 0) { mWindow.setUiOptions(info.uiOptions); } mUiThread = Thread.currentThread(); mMainThread = aThread; mInstrumentation = instr; mToken = token; mIdent = ident; mApplication = application; mIntent = intent; mReferrer = referrer; mComponent = intent.getComponent(); mActivityInfo = info; mTitle = title; mParent = parent; mEmbeddedID = id; mLastNonConfigurationInstances = lastNonConfigurationInstances; if (voiceInteractor != null) { if (lastNonConfigurationInstances != null) { mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor; } else { mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this, Looper.myLooper()); } } 2、mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); if (mParent != null) { mWindow.setContainer(mParent.getWindow()); } 3、mWindowManager = mWindow.getWindowManager(); mCurrentConfig = config; }Window类: public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) { mAppToken = appToken; mAppName = appName; mHardwareAccelerated = hardwareAccelerated || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false); if (wm == null) { wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); } 4、 mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); }WindowManagerImpl类: * Provides low-level communication with the system window manager for * operations that are bound to a particular context, display or parent window. * Instances of this object are sensitive to the compatibility info associated * with the running application.提供一个跟系统WindowManager低等级的沟通方式,用于特别的Context、Display或者父Window。 WindowManagerGlobal类的解释同上。 public final class WindowManagerImpl implements WindowManager { private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); @Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); } @Override public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.updateViewLayout(view, params); } private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) { // Only use the default token if we don't have a parent window. if (mDefaultToken != null && mParentWindow == null) { if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } // Only use the default token if we don't already have a token. final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; if (wparams.token == null) { wparams.token = mDefaultToken; } } } @Override public void removeView(View view) { mGlobal.removeView(view, false); }

优秀的个人博客,低调大师

AI+ 云原生:构建弹性可扩展的智能应用架构

人工智能模型的训练与推理正从单机实验走向生产级部署,而云原生技术(容器、编排、服务网格、可观测性)为这一转变提供了标准化的基础设施底座。AI + 云原生 的核心价值在于:通过Kubernetes的声明式API管理GPU资源,利用自动伸缩(HPA/VPA)应对推理流量的潮汐波动,借助服务网格实现灰度发布与流量镜像,最终将AI生命周期(数据准备、训练、调优、部署、监控)无缝融入DevOps流水线。

优秀的个人博客,低调大师

用户登录只需 5 行代码,高度可扩展!

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 annotationsfrom typing import Anyfrom asyncio import runfrom teo import Appfrom entities import Teo, User async def main(): app = App() async def validate_auth_code(checker_args: dict[str, Any], _, user: User, teo: Teo): finder = {} if checker_args['ids'].get('email') is not None: finder['email'] = user.email if checker_args['ids'].get('phoneNumber') is not None: finder['phoneNumber'] = user.phone_number auth_code = await teo.auth_code.find_unique({ "where": finder }) if auth_code is None: 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; use entities::{Teo, User};use indexmap::indexmap;use tokio::main;use teo::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为我们点一颗星。 Gitee: https://gitee.com/teocloud/teo 联系我们 在使用过程中,如果遇到任何困难,想要与我们沟通,或是提出功能需求,是非常容易的。带着点赞截图和公众号关注截图,添加我们的微信群群管微信caofz007,即可加入我们的用户群。 加入我们团队 目前我们都是以志愿者形式参与的开发,我是项目的发起者和主要编写者,框架核心,语言绑定,编辑器插件,网站都是我全职编写的。我们需要核心开发,前端开发,编辑器插件开发等人手,来把它做得更好。 我们现在有清晰的开发和推广,和商业化路线。在Teo具有一定规模和可见的良好未来的时候,即在具有一定底牌、资格、合适的时候,我们会积极努力寻找融资,把它做大。也会给予我们的项目贡献者,符合贡献价值的奖励。添加我们的伙伴caofz007这个微信,加入我们,成为我们的一部分。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册