Dart 的服务端开发

本文视频
本文已录制视频上传B站,请配合视频食用 《Dart 全栈之服务端》


本文是博主《Flutter全栈式开发》系列课程的拓展。我们在Flutter课程中,编写了一个注册登录案例,详细讲解了常见的注册、登录、API接口鉴权功能,前后端是如何配合实现的。其中后台服务使用了Dart语言中较为知名的aqueduct框架。

近来由于Dart版本迭代过于频繁和激烈,尤其是不兼容的空安全特性,导致课程中使用的服务端框架aqueduct停止维护。维护该框架的公司没有多余的精力使之兼容新版本,目前亦无社区接手项目,可能永久停止。相继的,Dart的第二大服务端框架angel亦停止维护,不得不说是Dart的一大遗憾。我们的课程已经发布有较长时间,其中的案例,现在只能使用旧版本才能运行。

为了大家有更好的课程体验,能持续跟进Dart的新特性,我特别编写了一个轻量级的支持空安全特性的Dart HTTP服务器框架:arowana,它可以直接在Flutter中使用,可以运行在移动端。

arowana这个单词是龙鱼的意思。龙鱼最早见于我国山海经记载:

“龙鱼陵居在其北,状如狸。一曰“鱼段”。即有神圣乘此以行九野。”

龙鱼有须,胸鳍似龙爪,加上浑身金鳞(或红鳞),因而形似神龙。加上其盘旋灵动,华贵端庄,静时安若处子,动时迅如脱兔,所以其神更似神龙。目前龙鱼为濒危物种,列入《世界自然保护联盟》(IUCN) 濒危物种红色名录。

arowana 框架基于Dart语言官方提供的 shelf 库,以处理HTTP请求,但shelf功能较弱,arowana 对其进行了部分功能增强。同时,还参考了我较为欣赏的Go语言Gin框架,编写了一个基于前缀树搜索的高性能路由组件,完全避免了正则匹配。我们知道,Dart语言中的正则处理性能较差,在AOT编译时可能还会称为性能瓶颈。arowana中还参考了aqueduct,封装了对Isolate并发处理的操作,使得并发处理更加简单。arowana没有太多依赖,尤其没有依赖一些动态特性(反射),因此它可以在移动端运行。

众所周知,Dart语言非常缺乏一个好的数据库ORM框架,其中一部分原因是因为Dart语言反射能力较差,甚至在Flutter中无法使用反射。因此arowana 并未提供或整合数据库模块,大家可自由组合目前可用的数据库连接模块,使用SQL操作数据。下面看一个简单示例:

class MyAChannel extends DefaultChannel {@overrideFuture prepare() async {print('current isolate [${Isolate.current.debugName}]');}@overridevoid entryPoint() {// 注册get请求路由get('/hello', (r) {return Response.ok('<h1>Hello, arowana!</h1>',headers: {'content-type': 'text/html; charset=UTF-8'});});}
}

在Flutter中使用:

void main() {var app = Application(MyAChannel());// numberOfInstances: 启动两个后台isolate处理请求app.start(numberOfInstances: 2,consoleLogging: true);runApp(const MyApp());
}

注册登录实现

来看一个完整例子,我们用arowana重写《Flutter全栈式开发》课程中的注册登录案例:

添加依赖。目前arowana处于试验版,没有发布仓库,使用Git协议添加依赖:

dependencies:dart_jsonwebtoken: ^2.3.2arowana:git: https://github.com/arcticfox1919/arowana.gitsqlite3: ^1.2.0crypto: ^3.0.1

代码结构:

首先编写一个Channel,注册路由:

/// server.dartclass MyAChannel extends DefaultChannel{late SqliteDb db;@overrideFuture prepare() async{db = SqliteDb.Connect();}@overridevoid entryPoint() {post('/register', RegisterController(db));post('/login', LoginController(db));// 分组路由var r = group('/info');// 给该组设置鉴权中间件r.use(Auth.bearer(AuthVerifier()));r.get('/list', (request){return ResponseX.ok({'language': [{'id': 1, 'name': 'dart'},{'id': 2, 'name': 'java'},{'id': 3, 'name': 'c'},{'id': 4, 'name': 'golang'},{'id': 5, 'name': 'python'}]});});}
}

路由使用,与Gin相似,这里使用了分组路由,给'/info'下的所有子路由设置了一个身份验证的中间件,访问子路由必须鉴权。arowana 提供了一个简单的身份验证中间件Auth。开发者只需要实现一个自己的验证器AuthVerifier,从而自定义验证逻辑,它需要继承自AuthValidator

两个post请求的路由分别实现注册和登录功能,两者逻辑相似,都使用json格式传递数据:

/// ctrl.dartclass RegisterController{SqliteDb db;RegisterController(this.db);Future<Response> call(Request request)async{var body = await request.body;var json = body.json;if (json != null) {// 从请求参数中构造User实体类var user = User.from(json);if (user.check()) {user.save(db);// 注册成功,给客户端返回令牌return ResponseX.token(user.generateToken());}}return ResponseX.badRequest('Invalid username or password!');}
}class LoginController{SqliteDb db;LoginController(this.db);Future<Response> call(Request request) async {var body = await request.body;var json = body.json;if (json != null) {var user = User.from(json);if (user.verify(db)) {// 登录成功,给客户端返回令牌return ResponseX.token(user.generateToken());}}return ResponseX.badRequest('Invalid username or password!');}
}

如果你使用表单提交用户名和密码,你的请求头中需要指定application/x-www-form-urlencode类型,在服务端,你需要使用下面的方式获取参数:

var body = await request.body;
var form = body.formParams;
if (form != null) {print(form['uname']);print(form['passwd']);
}

看一下User类的逻辑:

class User {int? id;String? uname;String? passwd;User.from(Map<String, dynamic> json) {uname = json['uname'];passwd = json['passwd'];}// 检查是否提交了有效的用户名、密码bool check() =>uname != null &&uname!.isNotEmpty &&passwd != null &&passwd!.isNotEmpty;// 验证登录bool verify(SqliteDb db) {if (!check()) return false;// 对密码进行hash处理passwd = md5.convert(utf8.encode(passwd!)).toString();// 去数据库查询用户名是否注册,密码是否正确var u = db.getUser(this);if(u == null) return false;id = u.id;return true;}// 保存用户名、密码到数据库void save(SqliteDb db){if(check()){// hash passwdpasswd = md5.convert(utf8.encode(passwd!)).toString();var u = db.save(this);id = u.id;}}// 生成JWT格式令牌AuthToken generateToken({Duration expiration = const Duration(hours: 24)}) {var now = DateTime.now();var expirationDate = now.add(expiration);var t = createToken(this, expirationDate.millisecondsSinceEpoch~/1000);return AuthToken(t, now, expirationDate);}
}

逻辑比较简单,这里主要使用了JWT格式生成Token,我们Flutter课程中已经介绍过JWT,但课程当时的案例用的另一种OAuth2.0生成的Token。

最后看一下验证器的实现:

class AuthVerifier extends AuthValidator{@overrideFutureOr<Authorization?> validate<T>(AuthorizationParser<T> parser, T authorizationData) {if (parser is AuthorizationBearerParser) {return _verify(authorizationData as String);}throw ArgumentError("Invalid 'parser' for 'AuthValidator.validate'. Use 'AuthorizationBearerHeader'.");}FutureOr<Authorization?> _verify(String accessToken) async {try {// 校验客户端带来的Tokenfinal jwt = verifyToken(accessToken);return Authorization((jwt.payload as Map)['id'].toString(), this);} on JWTUndefinedError catch(e){print(e.error);// 校验失败,颁发的Token已经过期了,客户端需要刷新令牌或者重新登录if(e.error is JWTExpiredError){throw TokenExpiredException('Error: the token has expired, please refresh');}}}
}

这里,对JWT Token的处理,使用了另一个库 dart_jsonwebtoken。给Auth中间件提供我们自定义的验证器,当访问注册了身份验证中间件的子路由/info/list时,首先会校验请求的令牌,未登录或令牌过期,则校验失败,无权访问该路由。

最后,我们可以使用Postman工具来检验成果:

访问需要鉴权的接口

完整示例代码,请访问 这里


关注公众号:编程之路从0到1

课程后续会不断更新,持续跟进Flutter技术迭代,关注我,你不会失望!

或关注博主的视频课程

Dart 全栈之服务端相关推荐

  1. 联万物,+智能,为行业,华为云升级OceanConnect IoT全栈云服务

    [中国,上海,2019年9月19日] 9月18日,在HUAWEI CONNECT 2019期间,华为云CTO张宇昕在华为云峰会上升级OceanConnect IoT全栈云服务,发布包括端.边.管.云. ...

  2. 阿里灵杰融合智能算力,全栈AI服务为探索者铺路

    8月30日,阿里云宣布正式推出全栈智能计算解决方案"飞天智算平台",并启动两座超大规模智算中心,为各类科研和智能企业机构提供高效.开放.绿色的智能计算服务. 其中,在AI开发层,阿 ...

  3. 天翼云国产化全栈云服务 赋能数字中国建设

    近年来,IT行业发展势头迅猛,"IT国产化"是发展潮流.全国的产业向云平台延伸是大势所趋,目前国产化替代工作已经由单一简单场景向复杂的场景演进.10月19日,"5G+天翼 ...

  4. Dart编译技术在服务端的探索和应用

    前言 最近闲鱼技术团队在Flutter+Dart的多端一体化的基础上,实现了FaaS研发模式.Dart吸取了其它高级语言设计的精华,例如Smalltalk的Image技术.JVM的HotSpot和Da ...

  5. 打造全栈安全服务,华为云网站安全解决方案为什么值得选?

    随着企业对于网站安全性意识的崛起,华为云网站安全解决方案也逐渐被更多用户所熟知.据了解,该解决方案综合了华为云多款服务器资源,包含必备的Web应用防火墙.DDoS高防.企业主机安全.云证书管理服务四大 ...

  6. 【前后台】后台管理系统技术栈vue-element-admin+服务端用egg实现上传头像功能

    01 前言: 后台管理系统技术栈:vue-element-admin 服务端:NodeJS(Egg.js) 02 默认头像 公认默认头像: https://cube.elemecdn.com/9/c2 ...

  7. 微信小程序 基础3【组件化开发、自定义组件、全栈开发、使用Express】

    视频地址: https://www.bilibili.com/video/BV1cW411T7t6  [2018]学做小程序- 清华大学 https://www.bilibili.com/video/ ...

  8. 精彩回顾 |全栈人工智能、区块链等服务亮相2019华为云峰会香港站

    [中国,香港,2019年3月19日] 华为今天在香港举办2019华为云香港峰会,将投入更多技术和资源服务香港及亚太市场,助力香港打造无所不及的智能,以云+AI助推粤港澳大湾区进入智能时代,共创未来.来 ...

  9. 盘点2018年云计算热点:云原生、全栈云,云大脑,谁能独占鳌头?

    一转眼2018年已经快要走到了尽头,白了头,累了心,但依然心怀憧憬. 这一年来,企业服务圈内可谓是风云变幻,各类新产品.新技术.新模式,甚至新概念层出不穷. 作为企业服务领域的从业者,整个2018年, ...

最新文章

  1. Ubuntu命令终端查看使用过的命令
  2. ajax修改按钮的html值,表格行的按钮AJAX后,怎么修改表格当前行的值
  3. 安装kali linux 2017.1 【二、安装VMware-tools 以及相关问题处理】
  4. 【华为云技术分享】处理器存储模型概述(1)
  5. linux contos7防火墙加端口,Linux:centos7防火墙开放端口
  6. 程序设计习惯养成计划---二、测试代码
  7. iOS 提交app到iTunes Connect预览截图截取方法及尺寸大小
  8. 【考研计算机网络】 强化笔记
  9. 2021年进销存管理软件商户门店使用热度前十名排行榜
  10. Android 没有出现menu 按键显示 解决
  11. vue 加载720全景图
  12. 我转行程序员的那一年(六)
  13. T229473 D. 背单词的小智(二分)
  14. java代码随机取名字
  15. happen-before原则解读
  16. 3G到5G,运营商的“失落十年”
  17. java时区ZoneId集
  18. 安装MATLAB时报错:提取错误 安装dsp_doc_en_common时检测到以下错误:某安装路径(指定的路径无效)
  19. ubuntu下安装fcitx五笔输入法
  20. 1.试用期个人工作总结(篇一)

热门文章

  1. android弹球游戏前台代码,Android 实现弹球游戏
  2. json与MYSQL
  3. 数字低通滤波器的原理及实现
  4. VBA_突然运行速度很慢很慢【已解决】
  5. 尚硅谷_谷粒学苑-微服务+全栈在线教育实战项目之旅
  6. 不适定问题(ill-posed)
  7. 十行代码,京东秒杀,还有什么比Python牛逼呢?
  8. 文学研究、教书育儿、码字写作必备神器
  9. JS 案例 飘浮广告
  10. cisco packet tracer_sign up(基于临时邮箱/qq邮箱注册并成功在软件中登录)(2021.9实践)(应对Sorry, we can‘t find a NetAcad...)